From: beeanyew Date: Fri, 11 Jun 2021 19:38:36 +0000 (+0200) Subject: Merge branch 'wip-crap' into main X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=62d18f9f13989b4625a00c1eb050301f8470cf94;hp=fe3b4bb032e02148c44ea11a23129650eae7efd7;p=pistorm Merge branch 'wip-crap' into main --- diff --git a/Makefile b/Makefile index ca69756..7396390 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ EXENAME = emulator MAINFILES = emulator.c \ memory_mapped.c \ config_file/config_file.c \ + config_file/rominfo.c \ input/input.c \ gpio/ps_protocol.c \ platforms/platforms.c \ @@ -15,11 +16,13 @@ MAINFILES = emulator.c \ platforms/amiga/hunk-reloc.c \ platforms/amiga/cdtv-dmac.c \ platforms/amiga/rtg/rtg.c \ - platforms/amiga/rtg/rtg-output.c \ + platforms/amiga/rtg/rtg-output-raylib.c \ platforms/amiga/rtg/rtg-gfx.c \ platforms/amiga/piscsi/piscsi.c \ + platforms/amiga/pistorm-dev/pistorm-dev.c \ platforms/amiga/net/pi-net.c \ - platforms/shared/rtc.c + platforms/shared/rtc.c \ + platforms/shared/common.c MUSASHIFILES = m68kcpu.c m68kdasm.c softfloat/softfloat.c softfloat/softfloat_fpsp.c MUSASHIGENCFILES = m68kops.c @@ -32,12 +35,25 @@ EXE = EXEPATH = ./ .CFILES = $(MAINFILES) $(MUSASHIFILES) $(MUSASHIGENCFILES) -.OFILES = $(.CFILES:%.c=%.o) +.OFILES = $(.CFILES:%.c=%.o) a314/a314.o CC = gcc +CXX = g++ WARNINGS = -Wall -Wextra -pedantic -CFLAGS = $(WARNINGS) -I. -march=armv8-a -mfloat-abi=hard -mfpu=neon-fp-armv8 -O3 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -LFLAGS = $(WARNINGS) `sdl2-config --libs` + +# Pi3 CFLAGS +CFLAGS = $(WARNINGS) -I. -I./raylib -I./raylib/external -march=armv8-a -mfloat-abi=hard -mfpu=neon-fp-armv8 -O3 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -lstdc++ +# Pi4 CFLAGS +#CFLAGS = $(WARNINGS) -I. -I./raylib_pi4_test -I./raylib_pi4_test/external -march=armv8-a -mfloat-abi=hard -mfpu=neon-fp-armv8 -O3 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE + +# Old SDL2 stuff +#LFLAGS = $(WARNINGS) `sdl2-config --libs` + +# Pi3 standard raylib stuff +LFLAGS = $(WARNINGS) -L/opt/vc/lib -L./raylib -lraylib -lbrcmGLESv2 -lbrcmEGL -lbcm_host -lstdc++ +# Pi4 experimental crap +# Graphics output on the Pi4 sort of REQUIRES X11 to be running, otherwise it is insanely slow and useless. +#LFLAGS = $(WARNINGS) -L/usr/local/lib -L./raylib_pi4_test -lraylib -lGL -ldl -lrt -lX11 -DPLATFORM_DESKTOP TARGET = $(EXENAME)$(EXE) @@ -51,7 +67,10 @@ clean: $(TARGET): $(MUSASHIGENHFILES) $(.OFILES) Makefile - $(CC) -o $@ $(.OFILES) -O3 -pthread $(LFLAGS) -lm + $(CC) -o $@ $(.OFILES) -O3 -pthread $(LFLAGS) -lm -lstdc++ + +a314/a314.o: a314/a314.cc a314/a314.h + $(CXX) -c -o a314/a314.o -O3 a314/a314.cc -march=armv8-a -mfloat-abi=hard -mfpu=neon-fp-armv8 -O3 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -I. -I.. $(MUSASHIGENCFILES) $(MUSASHIGENHFILES): $(MUSASHIGENERATOR)$(EXE) $(EXEPATH)$(MUSASHIGENERATOR)$(EXE) diff --git a/a314/README.md b/a314/README.md new file mode 100644 index 0000000..4548455 --- /dev/null +++ b/a314/README.md @@ -0,0 +1,239 @@ +# **A314 emulation support** + +## **About** + +A314 is a trapdoor expansion board for Amiga 500 created by Niklas Ekström, It also uses a Raspberry Pi connected to bring interesting features to the Amiga. For more information about the board visit the [A314 Github](https://github.com/niklasekstrom/a314). + +PiStorm provides the functionality of the A314 emulating the device on the Pi side. + +## **Installation** + +Enable the A314 emulation on your cfg file by uncommenting the line, by removing the #: + +``` +setvar a314 +``` + +All A314 features will require that the file [`a314.device`](a314device/software-amiga/a314.device) is copied into DEVS: on the Amiga. + +For your convenience all the Amiga required files are available on the PiStorm disk mounted by default. Make sure you have the following line uncommented on your cfg file: + +``` +setvar piscsi6 platforms/amiga/pistorm.hdf +``` + +Open the Amiga shell and run: + +``` +copy PISTORM:a314/a314.device DEVS: +``` + +***[OPTIONAL]*** If you need to change the location of your [`a314d.conf`](files_pi/a314d.conf) you can do it by uncommenting the line on your cfg file, by removing the #, the path can be relative to the folder the emulator is launched or absolute: + +``` +setvar a314_conf /home/pi/amiga-files/a314/files_pi/a314d.conf +``` + +--- + +### **a314fs** + +a34is a file system that is mounted in AmigaDOS as a device, PI0: +The volume in PI0: is called PiDisk:, and is mapped to a directory in the RPi. + +The default shared folder in the Pi is at `/home/pi/pistorm/data/a314-shared` + +To enable it, on the Amiga you need to: + +- Copy [`a314fs`](a314device/software-amiga/a314fs) to L: +- Append the contents of [`a314fs-mountlist`](a314device/software-amiga/a314fs-mountlist) to DEVS:Mountlist + +This can be achieved by opening the Amiga shell and running the commands: + +``` +copy PISTORM:a314/a314fs L: +type PISTORM:a314/a314fs-mountlist >> DEVS:Mountlist +``` + +Now you can mount your shared folder with + +``` +mount PI0: +``` + +You can add the mount command into S:Startup-Sequence if you want it mounted automatically on boot. + +**[OPTIONAL]** You can customise the location of your [`a314fs.conf`](files_pi/a314fs.conf) file by adding the parameter `-conf-file ` to the `a314fs.py` python command line in the a314d.conf file. These paths can be relative to the folder where the emultor is running or absolute. E.g.: + +``` +a314fs python3 /home/pi/pistorm/a314/files_pi/a314fs.py -conf-file /home/pi/amiga-files/config/a314fs.conf +``` + +The [`a314fs.conf`](files_pi/a314fs.conf) file allow you to define the location of your shared folder. This path can be relative to the folder where the emultor is running or absolute. It was supposed to allow you to give a different volume name and label on the amiga side, but this is currently not working. E.g.: + +``` +{ + "devices": { + "PI0": { + "volume": "PiDisk", + "path": "/home/pi/amiga-files/shared" + } + } +} +``` + +--- + +### **pi command** + +pi is a command that lets you invoke executables on the RPi from the Amiga side. For example, if your current working directory is on PiDisk: and you run "pi vc hello.c -o hello", then the vc program (the VBCC cross-compiler) is executed on the RPi with the given arguments. It will cross-compile “hello.c” into the Amiga executable file “hello”. The resulting binary is immediately accessible through the a314fs. + +You may also launch Interactive applications using the pi command, such as "pi mc -a" which will run Midnight Commander. Running pi without any arguments is equivalent to "pi bash" and will present you with a bash prompt from the RPi. + +To install the pi command just copy [`pi`](a314device/software-amiga/pi) to C: running the command: + +``` +copy PISTORM:a314/pi C: +``` + +--- + +### **ethernet** + +A SANA-II driver that forwards Ethernet packets to the network interface of the RPi. Together with an Amiga TCP/IP stack this provides network access to the Amiga. + +#### **On the Pi**: + +- Install the pip3 command: +``` +sudo apt install python3-pip +``` + +- Install python-pytun +``` +sudo pip3 install python-pytun +``` + +- Copy the [`tap0`](files_pi/eth-config-pi/tap) to `/etc/network/interfaces.d/`: +``` +sudo cp /home/pi/pistorm/a314/files_pi/eth-config-pi/tap0 /etc/network/interfaces.d/ +``` + +- Add NAT rulest to forward the requests from the amiga into the wi-fi: +``` +sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE +sudo iptables -A FORWARD -i wlan0 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT +sudo iptables -A FORWARD -i tap0 -o wlan0 -j ACCEPT +``` + +- Make the NAT rules persintent: +``` +sudo apt install iptables-persistent +``` +Agree to the IPv4 question, You can pick any answer for IPv6 + +- Enable IPv4 forwarding by editing /etc/sysctl.conf with: +``` +sudo nano /etc/sysctl.conf +``` +Uncomment the line, by removing the #: +``` +net.ipv4.ip_forward=1 +``` + +- Reboot the Pi. + +#### **On the Amiga**: + +- Copy the [`a314eth.device`](a314device/software-amiga/a314eth.device) to DEVS: +``` +copy PISTORM:a314/a314eth.device DEVS; +``` + +- The ethernet emulated interface is installed and configured both on the Pi and the Amiga. You'll need a TCP/IP stack installed and configured on the Amiga to be abe to access the internet. + +- **[Instructions for [Roadshow](http://roadshow.apc-tcp.de/index-en.php)**] +Roadshow is a proprietary software that costs €25.00. There's also a demo version that will shutdown after 15 minutes of usage. + - Install Roadshow. If you already have Roadshow installed and configured on your HD image manually add the content of the following files instead of copying their contents over, or you will lose your current network setup. + - Copy [`A314Eth`](software-amiga/eth-config-amiga/A314Eth) to DEVS:NetInterfaces/ + ``` + COPY PISTORM:a314/eth-config-amiga/A314Eth DEVS:NetInterfaces/ + ``` + - Copy [`routes`](software-amiga/eth-config-amiga/routes) to DEVS:Internet/ + ``` + COPY PISTORM:a314/eth-config-amiga/routes DEVS:Internet/ + ``` + - Copy [`name-resolution`](software-amiga/eth-config-amiga/name_resolution) to DEVS:Internet/ + - Congratulations, you have set the a314 networking on your pistorm. Check if everything worked on your Amiga Shell: + ``` + ping www.google.com + ``` +- + +- **[Instructions for [AmiTCP](https://aminet.net/package/comm/net/AmiTCP-bin-30b2)**] + - Copy devicefiles: + ``` + copy pistorm:a314/a314.device DEVS: + copy pistorm:a314/a314eth.device DEVS: + ``` + Start Install AmiTCP-3.0b2: + - Intermediate User -> Proceed With Install + - Install for Real, Log None -> Proceed + - Select directory where to install: Work:AmiTCP-3.0b2 -> Proceed + - Confirm the copy -> Skip This Part (do 4 times) + - Do you want to install example Sana-II configuration files? -> Yes + - Select Sana-II configuration files to be copied: -> Proceed with Copy + - Do you want to install Napsaterm fonts -> Yes + - Select directory where to install Napsaterm fonts: fonts: -> Proceed + - Logging in as 'root'... -> Proceed + - Give empty password and close login window + - Enter default user name: user -> Proceed + - Enter the host name of your computer: amiga -> Proceed + - Enter domain part of your host name: local -> Proceed + - The host name "amiga.local" will be stored... -> Store to ENV(ARC): + - Give aliases... (leave empty) -> Proceed + - Select a SANA-II device driver: -> Parent Drawer -> a314eth.device -> Proceed + - Select unit number: 0 -> Proceed + - IP address: 192.168.2.2 -> Proceed + - Give destination address... (leave empty) -> Proceed + - Netmask of network on interface: 255.255.255.0 -> Proceed + - Is this correct? -> Yes + - Select a SANA-II device driver: (leave empty) -> Proceed + - Enter the IP address of the default gateway: 192.168.2.1 -> Proceed + - Give domain names: (leave default) -> Proceed + - Give domain names: (leave empty) -> Proceed + - Give the IP addresses of the name servers: 1.1.1.1 -> Proceed + - Give the IP addresses of the name servers: 1.0.0.1 -> Proceed + - Give the IP addresses of the name servers: (leave empty) -> Proceed + - Do you want the AmiTCP/IP to be started at the system startup? -> Yes + - Do you want Installer to make the rquired chages to yout s:user-startup script? -> Yes + - Do you want the Inetd to be started at the AmiTCP/IP startup -> Yes + - Quit readme with q-key. + - Installation complete! -> Proceed + + `ed s:user-startup` + - Remove login-line (ESC,D,enter) + - Save and exit (ESC,x,enter) + + `ed Work:AmiTCP-3.0b2/db/interfaces` + - Add line: eth dev=devs:a314eth.device + - Save and exit (ESC,x,enter) + + `ed Work:AmiTCP-3.0b2/bin/startnet` + - modify ifconfig-lines: + ``` + AmiTCP:bin/ifconfig lo0 localhost + AmiTCP:bin/ifconfig eth0 192.168.2.2 netmask 255.255.255.0 + ``` + - Make sure that startnet executable bit is set: + ``` + protect Work:AmiTCP-3.0b2/bin/startnet +s + ``` + - Congratulations, you have set the a314 networking on your pistorm. Check if everything worked on your Amiga Shell: + ``` + ping www.google.com + ``` + +### PiAudio, RemoteWB, VideoPlayer are **not supported, and it's unlikely it will ever be as the PiStorm doesn't have full access to the Chip RAM bus.** + +--- diff --git a/a314/a314.cc b/a314/a314.cc new file mode 100644 index 0000000..38f312a --- /dev/null +++ b/a314/a314.cc @@ -0,0 +1,1427 @@ +/* + * Copyright 2020-2021 Niklas Ekström + * Based on a314d daemon for A314. + */ + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "a314.h" +// Silence stupid warning +#undef _GNU_SOURCE +#include "config_file/config_file.h" + +extern "C" emulator_config *cfg; + +#define LOGGER_TRACE 1 +#define LOGGER_DEBUG 2 +#define LOGGER_INFO 3 +#define LOGGER_WARN 4 +#define LOGGER_ERROR 5 + +#define LOGGER_SHOW LOGGER_INFO + +#define logger_trace(...) do { if (LOGGER_TRACE >= LOGGER_SHOW) fprintf(stdout, __VA_ARGS__); } while (0) +#define logger_debug(...) do { if (LOGGER_DEBUG >= LOGGER_SHOW) fprintf(stdout, __VA_ARGS__); } while (0) +#define logger_info(...) do { if (LOGGER_INFO >= LOGGER_SHOW) fprintf(stdout, __VA_ARGS__); } while (0) +#define logger_warn(...) do { if (LOGGER_WARN >= LOGGER_SHOW) fprintf(stdout, __VA_ARGS__); } while (0) +#define logger_error(...) do { if (LOGGER_ERROR >= LOGGER_SHOW) fprintf(stderr, __VA_ARGS__); } while (0) + +// Events that are communicated via IRQ from Amiga to Raspberry. +#define R_EVENT_A2R_TAIL 1 +#define R_EVENT_R2A_HEAD 2 +#define R_EVENT_STARTED 4 + +// Events that are communicated from Raspberry to Amiga. +#define A_EVENT_R2A_TAIL 1 +#define A_EVENT_A2R_HEAD 2 + +// Offset relative to communication area for queue pointers. +#define A2R_TAIL_OFFSET 0 +#define R2A_HEAD_OFFSET 1 +#define R2A_TAIL_OFFSET 2 +#define A2R_HEAD_OFFSET 3 + +// Packets that are communicated across physical channels (A2R and R2A). +#define PKT_CONNECT 4 +#define PKT_CONNECT_RESPONSE 5 +#define PKT_DATA 6 +#define PKT_EOS 7 +#define PKT_RESET 8 + +// Valid responses for PKT_CONNECT_RESPONSE. +#define CONNECT_OK 0 +#define CONNECT_UNKNOWN_SERVICE 3 + +// Messages that are communicated between driver and client. +#define MSG_REGISTER_REQ 1 +#define MSG_REGISTER_RES 2 +#define MSG_DEREGISTER_REQ 3 +#define MSG_DEREGISTER_RES 4 +#define MSG_READ_MEM_REQ 5 +#define MSG_READ_MEM_RES 6 +#define MSG_WRITE_MEM_REQ 7 +#define MSG_WRITE_MEM_RES 8 +#define MSG_CONNECT 9 +#define MSG_CONNECT_RESPONSE 10 +#define MSG_DATA 11 +#define MSG_EOS 12 +#define MSG_RESET 13 + +#define MSG_SUCCESS 1 +#define MSG_FAIL 0 + +static sigset_t original_sigset; + +static pthread_t thread_id; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static int server_socket = -1; + +static int epfd = -1; +static int irq_fds[2]; + +extern "C" unsigned int ps_read_8(unsigned int address); +extern "C" void ps_write_8(unsigned int address, unsigned int value); +extern "C" void ps_write_16(unsigned int address, unsigned int value); + +unsigned int a314_base; +int a314_base_configured; + +struct ComArea +{ + uint8_t a_events; + uint8_t a_enable; + uint8_t r_events; + uint8_t r_enable; // Unused. + + uint32_t mem_base; + uint32_t mem_size; + + uint8_t a2r_tail; + uint8_t r2a_head; + uint8_t r2a_tail; + uint8_t a2r_head; + + uint8_t a2r_buffer[256]; + uint8_t r2a_buffer[256]; +}; + +static ComArea ca; + +static bool a314_device_started = false; + +static uint8_t channel_status[4]; +static uint8_t channel_status_updated = 0; + +static uint8_t recv_buf[256]; +static uint8_t send_buf[256]; + +struct LogicalChannel; +struct ClientConnection; + +#pragma pack(push, 1) +struct MessageHeader +{ + uint32_t length; + uint32_t stream_id; + uint8_t type; +}; //} __attribute__((packed)); +#pragma pack(pop) + +struct MessageBuffer +{ + int pos; + std::vector data; +}; + +struct RegisteredService +{ + std::string name; + ClientConnection *cc; +}; + +struct PacketBuffer +{ + int type; + std::vector data; +}; + +struct ClientConnection +{ + int fd; + + int next_stream_id; + + int bytes_read; + MessageHeader header; + std::vector payload; + + std::list message_queue; + + std::list associations; +}; + +struct LogicalChannel +{ + int channel_id; + + ClientConnection *association; + int stream_id; + + bool got_eos_from_ami; + bool got_eos_from_client; + + std::list packet_queue; +}; + +static void remove_association(LogicalChannel *ch); +static void clear_packet_queue(LogicalChannel *ch); +static void create_and_enqueue_packet(LogicalChannel *ch, uint8_t type, uint8_t *data, uint8_t length); + +static std::list connections; +static std::list services; +static std::list channels; +static std::list send_queue; + +struct OnDemandStart +{ + std::string service_name; + std::string program; + std::vector arguments; +}; + +std::vector on_demand_services; + +std::string a314_config_file = "./a314/files_pi/a314d.conf"; +std::string home_env = "HOME=./"; + +static void load_config_file(const char *filename) +{ + FILE *f = fopen(filename, "rt"); + if (f == nullptr) { + return; + } + + char line[256]; + std::vector parts; + + while (fgets(line, 256, f) != nullptr) + { + char org_line[256]; + strcpy(org_line, line); + + bool in_quotes = false; + + int start = 0; + for (int i = 0; i < 256; i++) + { + if (line[i] == 0) + { + if (start < i) + parts.push_back(&line[start]); + break; + } + else if (line[i] == '"') + { + line[i] = 0; + if (in_quotes) + parts.push_back(&line[start]); + in_quotes = !in_quotes; + start = i + 1; + } + else if (isspace(line[i]) && !in_quotes) + { + line[i] = 0; + if (start < i) + parts.push_back(&line[start]); + start = i + 1; + } + } + + if (parts.size() >= 2) + { + on_demand_services.emplace_back(); + auto &e = on_demand_services.back(); + e.service_name = parts[0]; + e.program = parts[1]; + for (int i = 1; i < parts.size(); i++) + e.arguments.push_back(std::string(parts[i])); + } + else if (parts.size() != 0) + logger_warn("Invalid number of columns in configuration file line: %s\n", org_line); + + parts.clear(); + } + + fclose(f); + + if (on_demand_services.empty()) + logger_warn("No registered services\n"); +} + +static int init_server_socket() +{ + server_socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (server_socket == -1) + { + logger_error("Failed to create server socket\n"); + return -1; + } + + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(7110); + + int res = bind(server_socket, (struct sockaddr *)&address, sizeof(address)); + if (res < 0) + { + logger_error("Bind to localhost:7110 failed\n"); + return -1; + } + + listen(server_socket, 16); + + return 0; +} + +static void shutdown_server_socket() +{ + if (server_socket != -1) + close(server_socket); + server_socket = -1; +} + +void create_and_send_msg(ClientConnection *cc, int type, int stream_id, uint8_t *data, int length) +{ + MessageBuffer mb; + mb.pos = 0; + mb.data.resize(sizeof(MessageHeader) + length); + + MessageHeader *mh = (MessageHeader *)&mb.data[0]; + mh->length = length; + mh->stream_id = stream_id; + mh->type = type; + if (length && data) + memcpy(&mb.data[sizeof(MessageHeader)], data, length); + + if (!cc->message_queue.empty()) + { + cc->message_queue.push_back(std::move(mb)); + return; + } + + while (1) + { + int left = mb.data.size() - mb.pos; + uint8_t *src = &mb.data[mb.pos]; + ssize_t r = write(cc->fd, src, left); + if (r == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + cc->message_queue.push_back(std::move(mb)); + return; + } + else if (errno == ECONNRESET) + { + // Do not close connection here; it will get done at some other place. + return; + } + else + { + logger_error("Write failed unexpectedly with errno = %d\n", errno); + exit(-1); + } + } + + mb.pos += r; + if (r == left) + { + return; + } + } +} + +static void handle_msg_register_req(ClientConnection *cc) +{ + uint8_t result = MSG_FAIL; + + std::string service_name((char *)&cc->payload[0], cc->payload.size()); + + auto it = services.begin(); + for (; it != services.end(); it++) + if (it->name == service_name) + break; + + if (it == services.end()) + { + services.emplace_back(); + + RegisteredService &srv = services.back(); + srv.cc = cc; + srv.name = std::move(service_name); + + result = MSG_SUCCESS; + } + + create_and_send_msg(cc, MSG_REGISTER_RES, 0, &result, 1); +} + +static void handle_msg_deregister_req(ClientConnection *cc) +{ + uint8_t result = MSG_FAIL; + + std::string service_name((char *)&cc->payload[0], cc->payload.size()); + + for (auto it = services.begin(); it != services.end(); it++) + { + if (it->name == service_name && it->cc == cc) + { + services.erase(it); + result = MSG_SUCCESS; + break; + } + } + + create_and_send_msg(cc, MSG_DEREGISTER_RES, 0, &result, 1); +} + +uint8_t manual_read_buf[64 * SIZE_KILO]; + +static void handle_msg_read_mem_req(ClientConnection *cc) +{ + uint32_t address = *(uint32_t *)&(cc->payload[0]); + uint32_t length = *(uint32_t *)&(cc->payload[4]); + + if (get_mapped_item_by_address(cfg, address) != -1) { + int32_t index = get_mapped_item_by_address(cfg, address); + uint8_t *map = &cfg->map_data[index][address - cfg->map_offset[index]]; + create_and_send_msg(cc, MSG_READ_MEM_RES, 0, map, length); + } else { + // No idea if this actually works. + for (int i = 0; i < length; i++) { + manual_read_buf[i] = (unsigned char)ps_read_8(address + i); + } + create_and_send_msg(cc, MSG_READ_MEM_RES, 0, manual_read_buf, length); + } + +} + +static void handle_msg_write_mem_req(ClientConnection *cc) +{ + uint32_t address = *(uint32_t *)&(cc->payload[0]); + uint32_t length = cc->payload.size() - 4; + + if (get_mapped_item_by_address(cfg, address) != -1) { + int32_t index = get_mapped_item_by_address(cfg, address); + uint8_t *map = &cfg->map_data[index][address - cfg->map_offset[index]]; + memcpy(map, &(cc->payload[4]), length); + } else { + // No idea if this actually works. + for (int i = 0; i < length; i++) { + ps_write_8(address + i, cc->payload[4 + i]); + } + } + + create_and_send_msg(cc, MSG_WRITE_MEM_RES, 0, nullptr, 0); +} + +static LogicalChannel *get_associated_channel_by_stream_id(ClientConnection *cc, int stream_id) +{ + for (auto ch : cc->associations) + { + if (ch->stream_id == stream_id) + return ch; + } + return nullptr; +} + +static void handle_msg_connect(ClientConnection *cc) +{ + // We currently don't handle that a client tries to connect to a service on the Amiga. +} + +static void handle_msg_connect_response(ClientConnection *cc) +{ + LogicalChannel *ch = get_associated_channel_by_stream_id(cc, cc->header.stream_id); + if (!ch) + return; + + create_and_enqueue_packet(ch, PKT_CONNECT_RESPONSE, &cc->payload[0], cc->payload.size()); + + if (cc->payload[0] != CONNECT_OK) + remove_association(ch); +} + +static void handle_msg_data(ClientConnection *cc) +{ + LogicalChannel *ch = get_associated_channel_by_stream_id(cc, cc->header.stream_id); + if (!ch) + return; + + create_and_enqueue_packet(ch, PKT_DATA, &cc->payload[0], cc->header.length); +} + +static void handle_msg_eos(ClientConnection *cc) +{ + LogicalChannel *ch = get_associated_channel_by_stream_id(cc, cc->header.stream_id); + if (!ch || ch->got_eos_from_client) + return; + + ch->got_eos_from_client = true; + + create_and_enqueue_packet(ch, PKT_EOS, nullptr, 0); + + if (ch->got_eos_from_ami) + remove_association(ch); +} + +static void handle_msg_reset(ClientConnection *cc) +{ + LogicalChannel *ch = get_associated_channel_by_stream_id(cc, cc->header.stream_id); + if (!ch) + return; + + remove_association(ch); + + clear_packet_queue(ch); + create_and_enqueue_packet(ch, PKT_RESET, nullptr, 0); +} + +static void handle_received_message(ClientConnection *cc) +{ + switch (cc->header.type) + { + case MSG_REGISTER_REQ: + handle_msg_register_req(cc); + break; + case MSG_DEREGISTER_REQ: + handle_msg_deregister_req(cc); + break; + case MSG_READ_MEM_REQ: + handle_msg_read_mem_req(cc); + break; + case MSG_WRITE_MEM_REQ: + handle_msg_write_mem_req(cc); + break; + case MSG_CONNECT: + handle_msg_connect(cc); + break; + case MSG_CONNECT_RESPONSE: + handle_msg_connect_response(cc); + break; + case MSG_DATA: + handle_msg_data(cc); + break; + case MSG_EOS: + handle_msg_eos(cc); + break; + case MSG_RESET: + handle_msg_reset(cc); + break; + default: + // This is bad, probably should disconnect from client. + logger_warn("Received a message of unknown type from client\n"); + break; + } +} + +static void close_and_remove_connection(ClientConnection *cc) +{ + shutdown(cc->fd, SHUT_WR); + close(cc->fd); + + { + auto it = services.begin(); + while (it != services.end()) + { + if (it->cc == cc) + it = services.erase(it); + else + it++; + } + } + + { + auto it = cc->associations.begin(); + while (it != cc->associations.end()) + { + auto ch = *it; + + clear_packet_queue(ch); + create_and_enqueue_packet(ch, PKT_RESET, nullptr, 0); + + ch->association = nullptr; + ch->stream_id = 0; + + it = cc->associations.erase(it); + } + } + + for (auto it = connections.begin(); it != connections.end(); it++) + { + if (&(*it) == cc) + { + connections.erase(it); + break; + } + } +} + +static void remove_association(LogicalChannel *ch) +{ + auto &ass = ch->association->associations; + ass.erase(std::find(ass.begin(), ass.end(), ch)); + + ch->association = nullptr; + ch->stream_id = 0; +} + +static void clear_packet_queue(LogicalChannel *ch) +{ + if (!ch->packet_queue.empty()) + { + ch->packet_queue.clear(); + send_queue.erase(std::find(send_queue.begin(), send_queue.end(), ch)); + } +} + +static void create_and_enqueue_packet(LogicalChannel *ch, uint8_t type, uint8_t *data, uint8_t length) +{ + if (ch->packet_queue.empty()) + send_queue.push_back(ch); + + ch->packet_queue.emplace_back(); + + PacketBuffer &pb = ch->packet_queue.back(); + pb.type = type; + pb.data.resize(length); + if (data && length) + memcpy(&pb.data[0], data, length); +} + +static void handle_pkt_connect(int channel_id, uint8_t *data, int plen) +{ + for (auto &ch : channels) + { + if (ch.channel_id == channel_id) + { + // We should handle this in some constructive way. + // This signals that should reset all logical channels. + logger_error("Received a CONNECT packet on a channel that was believed to be previously allocated\n"); + exit(-1); + } + } + + channels.emplace_back(); + + auto &ch = channels.back(); + + ch.channel_id = channel_id; + ch.association = nullptr; + ch.stream_id = 0; + ch.got_eos_from_ami = false; + ch.got_eos_from_client = false; + + std::string service_name((char *)data, plen); + + for (auto &srv : services) + { + if (srv.name == service_name) + { + ClientConnection *cc = srv.cc; + + ch.association = cc; + ch.stream_id = cc->next_stream_id; + + cc->next_stream_id += 2; + cc->associations.push_back(&ch); + + create_and_send_msg(ch.association, MSG_CONNECT, ch.stream_id, data, plen); + return; + } + } + + for (auto &on_demand : on_demand_services) + { + if (on_demand.service_name == service_name) + { + int fds[2]; + int status = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + if (status != 0) + { + logger_error("Unexpectedly not able to create socket pair.\n"); + exit(-1); + } + + pid_t child = fork(); + if (child == -1) + { + logger_error("Unexpectedly was not able to fork.\n"); + exit(-1); + } + else if (child == 0) + { + close(fds[0]); + int fd = fds[1]; + + // FIXE: The user should be configurable. + setgid(1000); + setuid(1000); + putenv((char *)home_env.c_str()); + + std::vector args(on_demand.arguments); + args.push_back("-ondemand"); + args.push_back(std::to_string(fd)); + std::vector args_arr; + for (auto &arg : args) + args_arr.push_back(arg.c_str()); + args_arr.push_back(nullptr); + + execvp(on_demand.program.c_str(), (char* const*) &args_arr[0]); + } + else + { + close(fds[1]); + int fd = fds[0]; + + int status = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); + if (status == -1) + { + logger_error("Unexpectedly unable to set close-on-exec flag on client socket descriptor; errno = %d\n", errno); + exit(-1); + } + + status = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + if (status == -1) + { + logger_error("Unexpectedly unable to set client socket to non blocking; errno = %d\n", errno); + exit(-1); + } + + int flag = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)); + + connections.emplace_back(); + + ClientConnection &cc = connections.back(); + cc.fd = fd; + cc.next_stream_id = 1; + cc.bytes_read = 0; + + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLOUT | EPOLLET; + ev.data.fd = fd; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) != 0) + { + logger_error("epoll_ctl() failed unexpectedly with errno = %d\n", errno); + exit(-1); + } + + services.emplace_back(); + + RegisteredService &srv = services.back(); + srv.cc = &cc; + srv.name = std::move(service_name); + + ch.association = &cc; + ch.stream_id = cc.next_stream_id; + + cc.next_stream_id += 2; + cc.associations.push_back(&ch); + + create_and_send_msg(ch.association, MSG_CONNECT, ch.stream_id, data, plen); + return; + } + } + } + + uint8_t response = CONNECT_UNKNOWN_SERVICE; + create_and_enqueue_packet(&ch, PKT_CONNECT_RESPONSE, &response, 1); +} + +static void handle_pkt_data(int channel_id, uint8_t *data, int plen) +{ + for (auto &ch : channels) + { + if (ch.channel_id == channel_id) + { + if (ch.association != nullptr && !ch.got_eos_from_ami) + create_and_send_msg(ch.association, MSG_DATA, ch.stream_id, data, plen); + + break; + } + } +} + +static void handle_pkt_eos(int channel_id) +{ + for (auto &ch : channels) + { + if (ch.channel_id == channel_id) + { + if (ch.association != nullptr && !ch.got_eos_from_ami) + { + ch.got_eos_from_ami = true; + + create_and_send_msg(ch.association, MSG_EOS, ch.stream_id, nullptr, 0); + + if (ch.got_eos_from_client) + remove_association(&ch); + } + break; + } + } +} + +static void handle_pkt_reset(int channel_id) +{ + for (auto &ch : channels) + { + if (ch.channel_id == channel_id) + { + clear_packet_queue(&ch); + + if (ch.association != nullptr) + { + create_and_send_msg(ch.association, MSG_RESET, ch.stream_id, nullptr, 0); + remove_association(&ch); + } + + break; + } + } +} + +static void remove_channel_if_not_associated_and_empty_pq(int channel_id) +{ + for (auto it = channels.begin(); it != channels.end(); it++) + { + if (it->channel_id == channel_id) + { + if (it->association == nullptr && it->packet_queue.empty()) + channels.erase(it); + + break; + } + } +} + +static void handle_received_pkt(int ptype, int channel_id, uint8_t *data, int plen) +{ + if (ptype == PKT_CONNECT) + handle_pkt_connect(channel_id, data, plen); + else if (ptype == PKT_DATA) + handle_pkt_data(channel_id, data, plen); + else if (ptype == PKT_EOS) + handle_pkt_eos(channel_id); + else if (ptype == PKT_RESET) + handle_pkt_reset(channel_id); + + remove_channel_if_not_associated_and_empty_pq(channel_id); +} + +static bool receive_from_a2r() +{ + int head = channel_status[A2R_HEAD_OFFSET]; + int tail = channel_status[A2R_TAIL_OFFSET]; + int len = (tail - head) & 255; + if (len == 0) + return false; + + if (head < tail) + { + memcpy(recv_buf, &ca.a2r_buffer[head], len); + } + else + { + memcpy(recv_buf, &ca.a2r_buffer[head], 256 - head); + + if (tail != 0) + { + memcpy(&recv_buf[len - tail], &ca.a2r_buffer[0], tail); + } + } + + uint8_t *p = recv_buf; + while (p < recv_buf + len) + { + uint8_t plen = *p++; + uint8_t ptype = *p++; + uint8_t channel_id = *p++; + handle_received_pkt(ptype, channel_id, p, plen); + p += plen; + } + + channel_status[A2R_HEAD_OFFSET] = channel_status[A2R_TAIL_OFFSET]; + channel_status_updated |= A_EVENT_A2R_HEAD; + return true; +} + +static bool flush_send_queue() +{ + int tail = channel_status[R2A_TAIL_OFFSET]; + int head = channel_status[R2A_HEAD_OFFSET]; + int len = (tail - head) & 255; + int left = 255 - len; + + int pos = 0; + + while (!send_queue.empty()) + { + LogicalChannel *ch = send_queue.front(); + PacketBuffer &pb = ch->packet_queue.front(); + + int ptype = pb.type; + int plen = 3 + pb.data.size(); + + if (left < plen) + break; + + send_buf[pos++] = pb.data.size(); + send_buf[pos++] = ptype; + send_buf[pos++] = ch->channel_id; + memcpy(&send_buf[pos], &pb.data[0], pb.data.size()); + pos += pb.data.size(); + + ch->packet_queue.pop_front(); + + send_queue.pop_front(); + + if (!ch->packet_queue.empty()) + send_queue.push_back(ch); + else + remove_channel_if_not_associated_and_empty_pq(ch->channel_id); + + left -= plen; + } + + int to_write = pos; + if (!to_write) + return false; + + uint8_t *p = send_buf; + int at_end = 256 - tail; + if (at_end < to_write) + { + memcpy(&ca.r2a_buffer[tail], p, at_end); + p += at_end; + to_write -= at_end; + tail = 0; + } + + memcpy(&ca.r2a_buffer[tail], p, to_write); + tail = (tail + to_write) & 255; + + channel_status[R2A_TAIL_OFFSET] = tail; + channel_status_updated |= A_EVENT_R2A_TAIL; + return true; +} + +static void read_channel_status() +{ + channel_status[A2R_TAIL_OFFSET] = ca.a2r_tail; + channel_status[R2A_HEAD_OFFSET] = ca.r2a_head; + channel_status[R2A_TAIL_OFFSET] = ca.r2a_tail; + channel_status[A2R_HEAD_OFFSET] = ca.a2r_head; + channel_status_updated = 0; +} + +static void write_channel_status() +{ + if (channel_status_updated != 0) + { + ca.r2a_tail = channel_status[R2A_TAIL_OFFSET]; + ca.a2r_head = channel_status[A2R_HEAD_OFFSET]; + + pthread_mutex_lock(&mutex); + ca.a_events |= channel_status_updated; + pthread_mutex_unlock(&mutex); + + channel_status_updated = 0; + } +} + +static void close_all_logical_channels() +{ + send_queue.clear(); + + auto it = channels.begin(); + while (it != channels.end()) + { + LogicalChannel &ch = *it; + + if (ch.association != nullptr) + { + create_and_send_msg(ch.association, MSG_RESET, ch.stream_id, nullptr, 0); + remove_association(&ch); + } + + it = channels.erase(it); + } +} + +static void handle_a314_irq(uint8_t events) +{ + if (events == 0) + return; + + if (events & R_EVENT_STARTED) + { + if (!channels.empty()) + logger_info("Received STARTED event while logical channels are open -- closing channels\n"); + + close_all_logical_channels(); + a314_device_started = true; + } + + if (!a314_device_started) + return; + + read_channel_status(); + + bool any_rcvd = receive_from_a2r(); + bool any_sent = flush_send_queue(); + + if (any_rcvd || any_sent) + write_channel_status(); +} + +static void handle_client_connection_event(ClientConnection *cc, struct epoll_event *ev) +{ + if (ev->events & EPOLLERR) + { + logger_warn("Received EPOLLERR for client connection\n"); + close_and_remove_connection(cc); + return; + } + + if (ev->events & EPOLLIN) + { + while (1) + { + int left; + uint8_t *dst; + + if (cc->payload.empty()) + { + left = sizeof(MessageHeader) - cc->bytes_read; + dst = (uint8_t *)&(cc->header) + cc->bytes_read; + } + else + { + left = cc->header.length - cc->bytes_read; + dst = &cc->payload[cc->bytes_read]; + } + + ssize_t r = read(cc->fd, dst, left); + if (r == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + + logger_error("Read failed unexpectedly with errno = %d\n", errno); + exit(-1); + } + + if (r == 0) + { + logger_info("Received End-of-File on client connection\n"); + close_and_remove_connection(cc); + return; + } + else + { + cc->bytes_read += r; + left -= r; + if (!left) + { + if (cc->payload.empty()) + { + if (cc->header.length == 0) + { + logger_trace("header: length=%d, stream_id=%d, type=%d\n", cc->header.length, cc->header.stream_id, cc->header.type); + handle_received_message(cc); + } + else + { + cc->payload.resize(cc->header.length); + } + } + else + { + logger_trace("header: length=%d, stream_id=%d, type=%d\n", cc->header.length, cc->header.stream_id, cc->header.type); + handle_received_message(cc); + cc->payload.clear(); + } + cc->bytes_read = 0; + } + } + } + } + + if (ev->events & EPOLLOUT) + { + while (!cc->message_queue.empty()) + { + MessageBuffer &mb = cc->message_queue.front(); + + int left = mb.data.size() - mb.pos; + uint8_t *src = &mb.data[mb.pos]; + ssize_t r = write(cc->fd, src, left); + if (r == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + else if (errno == ECONNRESET) + { + close_and_remove_connection(cc); + return; + } + else + { + logger_error("Write failed unexpectedly with errno = %d\n", errno); + exit(-1); + } + } + + mb.pos += r; + if (r == left) + cc->message_queue.pop_front(); + } + } +} + +static void handle_server_socket_ready() +{ + struct sockaddr_in address; + int alen = sizeof(struct sockaddr_in); + + int fd = accept(server_socket, (struct sockaddr *)&address, (socklen_t *)&alen); + if (fd < 0) + { + logger_error("Accept failed unexpectedly with errno = %d\n", errno); + exit(-1); + } + + int status = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); + if (status == -1) + { + logger_error("Unexpectedly unable to set close-on-exec flag on client socket descriptor; errno = %d\n", errno); + exit(-1); + } + + status = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + if (status == -1) + { + logger_error("Unexpectedly unable to set client socket to non blocking; errno = %d\n", errno); + exit(-1); + } + + int flag = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)); + + connections.emplace_back(); + + ClientConnection &cc = connections.back(); + cc.fd = fd; + cc.next_stream_id = 1; + cc.bytes_read = 0; + + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLOUT | EPOLLET; + ev.data.fd = fd; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) != 0) + { + logger_error("epoll_ctl() failed unexpectedly with errno = %d\n", errno); + exit(-1); + } +} + +static void main_loop() +{ + bool shutting_down = false; + bool done = false; + + while (!done) + { + struct epoll_event ev; + int timeout = shutting_down ? 10000 : -1; + int n = epoll_pwait(epfd, &ev, 1, timeout, &original_sigset); + if (n == -1) + { + if (errno == EINTR) + { + logger_info("Received SIGTERM\n"); + + shutdown_server_socket(); + + while (!connections.empty()) + close_and_remove_connection(&connections.front()); + + if (flush_send_queue()) + write_channel_status(); + + if (!channels.empty()) + shutting_down = true; + else + done = true; + } + else + { + logger_error("epoll_pwait failed with unexpected errno = %d\n", errno); + exit(-1); + } + } + else if (n == 0) + { + if (shutting_down) + done = true; + else + { + logger_error("epoll_pwait returned 0 which is unexpected since no timeout was set\n"); + exit(-1); + } + } + else + { + if (ev.data.fd == irq_fds[1]) + { + uint8_t events; + if (read(irq_fds[1], &events, 1) != 1) + { + logger_error("Read from interrupt socket pair, and unexpectedly didn't return 1 byte\n"); + exit(-1); + } + + handle_a314_irq(events); + } + else if (ev.data.fd == server_socket) + { + logger_trace("Epoll event: server socket is ready, events = %d\n", ev.events); + handle_server_socket_ready(); + } + else + { + logger_trace("Epoll event: client socket is ready, events = %d\n", ev.events); + + auto it = connections.begin(); + for (; it != connections.end(); it++) + { + if (it->fd == ev.data.fd) + break; + } + + if (it == connections.end()) + { + logger_error("Got notified about an event on a client connection that supposedly isn't currently open\n"); + exit(-1); + } + + ClientConnection *cc = &(*it); + handle_client_connection_event(cc, &ev); + + if (flush_send_queue()) + write_channel_status(); + } + } + } +} + +static int init_driver() +{ + if (init_server_socket() != 0) + return -1; + + int err = socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, irq_fds); + if (err != 0) + { + logger_error("Unable to create socket pair, errno = %d\n", errno); + return -1; + } + + epfd = epoll_create1(EPOLL_CLOEXEC); + if (epfd == -1) + return -1; + + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = irq_fds[1]; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, irq_fds[1], &ev) != 0) + return -1; + + ev.events = EPOLLIN; + ev.data.fd = server_socket; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, server_socket, &ev) != 0) + return -1; + + return 0; +} + +static void shutdown_driver() +{ + if (epfd != -1) + close(epfd); + + shutdown_server_socket(); +} + +static void *thread_start(void *arg) +{ + main_loop(); + shutdown_driver(); + return NULL; +} + +static void write_r_events(uint8_t events) +{ + if (write(irq_fds[0], &events, 1) != 1) + logger_error("Write to interrupt socket pair did not return 1\n"); +} + +int a314_init() +{ + load_config_file(a314_config_file.c_str()); + + int err = init_driver(); + if (err < 0) + { + shutdown_driver(); + return -1; + } + + err = pthread_create(&thread_id, NULL, thread_start, NULL); + if (err < 0) + { + logger_error("pthread_create failed with err = %d\n", err); + return -2; + } + + return 0; +} + +void a314_set_mem_base_size(unsigned int base, unsigned int size) +{ + ca.mem_base = htobe32(base); + ca.mem_size = htobe32(size); +} + +void a314_process_events() +{ + if (ca.a_events & ca.a_enable) + { + ps_write_16(0xdff09c, 0x8008); + m68k_set_irq(2); + } +} + +unsigned int a314_read_memory_8(unsigned int address) +{ + if (address >= sizeof(ca)) + return 0; + + uint8_t val; + if (address == offsetof(ComArea, a_events)) + { + pthread_mutex_lock(&mutex); + val = ca.a_events; + ca.a_events = 0; + pthread_mutex_unlock(&mutex); + } + else + { + uint8_t *p = (uint8_t *)&ca; + val = p[address]; + } + + return val; +} + +unsigned int a314_read_memory_16(unsigned int address) +{ + if (address >= sizeof(ca)) + return 0; + + uint16_t *p = (uint16_t *)&ca; + return be16toh(p[address >> 1]); +} + +unsigned int a314_read_memory_32(unsigned int address) +{ + if (address >= sizeof(ca)) + return 0; + + uint32_t *p = (uint32_t *)&ca; + return be32toh(p[address >> 2]); +} + +void a314_write_memory_8(unsigned int address, unsigned int value) +{ + if (address >= sizeof(ca)) + return; + + switch (address) + { + case offsetof(ComArea, a_events): + // a_events is not writable. + break; + + case offsetof(ComArea, r_events): + if (value != 0) + write_r_events((uint8_t)value); + break; + + default: + { + uint8_t *p = (uint8_t *)&ca; + p[address] = (uint8_t)value; + break; + } + } +} + +void a314_write_memory_16(unsigned int address, unsigned int value) +{ + // Not implemented. +} + +void a314_write_memory_32(unsigned int address, unsigned int value) +{ + // Not implemented. +} + +void a314_set_config_file(char *filename) +{ + printf ("[A314] Set A314 config filename to %s.\n", filename); + a314_config_file = std::string(filename); +} diff --git a/a314/a314.h b/a314/a314.h new file mode 100644 index 0000000..e05780d --- /dev/null +++ b/a314/a314.h @@ -0,0 +1,37 @@ +/* + * Copyright 2020-2021 Niklas Ekström + * A314 emulation header + */ + +#ifndef A314_H +#define A314_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define A314_ENABLED 1 + +extern unsigned int a314_base; +extern int a314_base_configured; + +#define A314_COM_AREA_SIZE (64 * 1024) + +int a314_init(); +void a314_set_mem_base_size(unsigned int base, unsigned int size); +void a314_process_events(); +void a314_set_config_file(char *filename); + +unsigned int a314_read_memory_8(unsigned int address); +unsigned int a314_read_memory_16(unsigned int address); +unsigned int a314_read_memory_32(unsigned int address); + +void a314_write_memory_8(unsigned int address, unsigned int value); +void a314_write_memory_16(unsigned int address, unsigned int value); +void a314_write_memory_32(unsigned int address, unsigned int value); + +#ifdef __cplusplus +} +#endif + +#endif /* A314_H */ diff --git a/a314/a314device/a314.h b/a314/a314device/a314.h new file mode 100644 index 0000000..e457f74 --- /dev/null +++ b/a314/a314device/a314.h @@ -0,0 +1,47 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#ifndef DEVICES_A314_H +#define DEVICES_A314_H + +#include + +#define A314_NAME "a314.device" + +#define A314_CONNECT (CMD_NONSTD+0) +#define A314_READ (CMD_NONSTD+1) +#define A314_WRITE (CMD_NONSTD+2) +#define A314_EOS (CMD_NONSTD+3) +#define A314_RESET (CMD_NONSTD+4) + +#define A314_CONNECT_OK 0 +#define A314_CONNECT_SOCKET_IN_USE 1 +#define A314_CONNECT_RESET 2 +#define A314_CONNECT_UNKNOWN_SERVICE 3 + +#define A314_READ_OK 0 +#define A314_READ_EOS 1 +#define A314_READ_RESET 2 + +#define A314_WRITE_OK 0 +#define A314_WRITE_EOS_SENT 1 +#define A314_WRITE_RESET 2 + +#define A314_EOS_OK 0 +#define A314_EOS_EOS_SENT 1 +#define A314_EOS_RESET 2 + +#define A314_RESET_OK 0 + +#define MEMF_A314 (1<<7) + +struct A314_IORequest +{ + struct IORequest a314_Request; + ULONG a314_Socket; + STRPTR a314_Buffer; + WORD a314_Length; +}; + +#endif diff --git a/a314/a314device/a314driver.c b/a314/a314device/a314driver.c new file mode 100644 index 0000000..176f504 --- /dev/null +++ b/a314/a314device/a314driver.c @@ -0,0 +1,623 @@ +/* + * Copyright 2018-2021 Niklas Ekström + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "a314.h" +#include "debug.h" +#include "device.h" +#include "protocol.h" +#include "sockets.h" +#include "fix_mem_region.h" +#include "startup.h" + +int used_in_r2a() +{ + return (ca->r2a_tail - ca->r2a_head) & 255; +} + +int used_in_a2r() +{ + return (ca->a2r_tail - ca->a2r_head) & 255; +} + +BOOL room_in_a2r(int len) +{ + return used_in_a2r() + 3 + len <= 255; +} + +void append_a2r_packet(UBYTE type, UBYTE stream_id, UBYTE length, UBYTE *data) +{ + UBYTE index = ca->a2r_tail; + ca->a2r_buffer[index++] = length; + ca->a2r_buffer[index++] = type; + ca->a2r_buffer[index++] = stream_id; + for (int i = 0; i < (int)length; i++) + ca->a2r_buffer[index++] = *data++; + ca->a2r_tail = index; +} + +void close_socket(struct Socket *s, BOOL should_send_reset) +{ + debug_printf("Called close socket\n"); + + if (s->pending_connect != NULL) + { + struct A314_IORequest *ior = s->pending_connect; + ior->a314_Request.io_Error = A314_CONNECT_RESET; + ReplyMsg((struct Message *)ior); + + s->pending_connect = NULL; + } + + if (s->pending_read != NULL) + { + struct A314_IORequest *ior = s->pending_read; + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_READ_RESET; + ReplyMsg((struct Message *)ior); + + s->pending_read = NULL; + } + + if (s->pending_write != NULL) + { + struct A314_IORequest *ior = s->pending_write; + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_WRITE_RESET; // A314_EOS_RESET == A314_WRITE_RESET + ReplyMsg((struct Message *)ior); + + s->pending_write = NULL; + } + + if (s->rq_head != NULL) + { + struct QueuedData *qd = s->rq_head; + while (qd != NULL) + { + struct QueuedData *next = qd->next; + FreeMem(qd, sizeof(struct QueuedData) + qd->length); + qd = next; + } + s->rq_head = NULL; + s->rq_tail = NULL; + } + + remove_from_send_queue(s); + + // No operations can be pending when SOCKET_CLOSED is set. + // However, may not be able to delete socket yet, because is waiting to send PKT_RESET. + s->flags |= SOCKET_CLOSED; + + BOOL should_delete_socket = TRUE; + + if (should_send_reset) + { + if (send_queue_head == NULL && room_in_a2r(0)) + { + append_a2r_packet(PKT_RESET, s->stream_id, 0, NULL); + } + else + { + s->flags |= SOCKET_SHOULD_SEND_RESET; + add_to_send_queue(s, 0); + should_delete_socket = FALSE; + } + } + + if (should_delete_socket) + delete_socket(s); +} + +// When a message is received on R2A it is written to this buffer, +// to avoid dealing with the issue that R2A is a circular buffer. +// This is somewhat inefficient, so may want to change that to read from R2A directly. +UBYTE received_packet[256]; + +static void handle_pkt_connect_response(UBYTE length, struct Socket *s) +{ + debug_printf("Received a CONNECT RESPONSE packet from rpi\n"); + + if (s->pending_connect == NULL) + { + debug_printf("SERIOUS ERROR: received a CONNECT RESPONSE even though no connect was pending\n"); + // Should reset stream? + } + else if (length != 1) + { + debug_printf("SERIOUS ERROR: received a CONNECT RESPONSE whose length was not 1\n"); + // Should reset stream? + } + else + { + UBYTE result = received_packet[0]; + if (result == 0) + { + struct A314_IORequest *ior = s->pending_connect; + ior->a314_Request.io_Error = A314_CONNECT_OK; + ReplyMsg((struct Message *)ior); + + s->pending_connect = NULL; + } + else + { + struct A314_IORequest *ior = s->pending_connect; + ior->a314_Request.io_Error = A314_CONNECT_UNKNOWN_SERVICE; + ReplyMsg((struct Message *)ior); + + s->pending_connect = NULL; + + close_socket(s, FALSE); + } + } +} + +static void handle_pkt_data(UBYTE length, struct Socket *s) +{ + debug_printf("Received a DATA packet from rpi\n"); + + if (s->pending_read != NULL) + { + struct A314_IORequest *ior = s->pending_read; + + if (ior->a314_Length < length) + close_socket(s, TRUE); + else + { + memcpy(ior->a314_Buffer, received_packet, length); + ior->a314_Length = length; + ior->a314_Request.io_Error = A314_READ_OK; + ReplyMsg((struct Message *)ior); + + s->pending_read = NULL; + } + } + else + { + struct QueuedData *qd = (struct QueuedData *)AllocMem(sizeof(struct QueuedData) + length, 0); + qd->next = NULL, + qd->length = length; + memcpy(qd->data, received_packet, length); + + if (s->rq_head == NULL) + s->rq_head = qd; + else + s->rq_tail->next = qd; + s->rq_tail = qd; + } +} + +static void handle_pkt_eos(struct Socket *s) +{ + debug_printf("Received a EOS packet from rpi\n"); + + s->flags |= SOCKET_RCVD_EOS_FROM_RPI; + + if (s->pending_read != NULL) + { + struct A314_IORequest *ior = s->pending_read; + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_READ_EOS; + ReplyMsg((struct Message *)ior); + + s->pending_read = NULL; + + s->flags |= SOCKET_SENT_EOS_TO_APP; + + if (s->flags & SOCKET_SENT_EOS_TO_RPI) + close_socket(s, FALSE); + } +} + +static void handle_r2a_packet(UBYTE type, UBYTE stream_id, UBYTE length) +{ + struct Socket *s = find_socket_by_stream_id(stream_id); + + if (s != NULL && type == PKT_RESET) + { + debug_printf("Received a RESET packet from rpi\n"); + close_socket(s, FALSE); + return; + } + + if (s == NULL || (s->flags & SOCKET_CLOSED)) + { + // Ignore this packet. The only packet that can do anything useful on a closed + // channel is CONNECT, which is not handled at this time. + return; + } + + if (type == PKT_CONNECT_RESPONSE) + { + handle_pkt_connect_response(length, s); + } + else if (type == PKT_DATA) + { + handle_pkt_data(length, s); + } + else if (type == PKT_EOS) + { + handle_pkt_eos(s); + } +} + +void handle_packets_received_r2a() +{ + while (used_in_r2a() != 0) + { + UBYTE index = ca->r2a_head; + + UBYTE len = ca->r2a_buffer[index++]; + UBYTE type = ca->r2a_buffer[index++]; + UBYTE stream_id = ca->r2a_buffer[index++]; + + for (int i = 0; i < len; i++) + received_packet[i] = ca->r2a_buffer[index++]; + + ca->r2a_head = index; + + handle_r2a_packet(type, stream_id, len); + } +} + +void handle_room_in_a2r() +{ + while (send_queue_head != NULL) + { + struct Socket *s = send_queue_head; + + if (!room_in_a2r(s->send_queue_required_length)) + break; + + remove_from_send_queue(s); + + if (s->pending_connect != NULL) + { + struct A314_IORequest *ior = s->pending_connect; + int len = ior->a314_Length; + append_a2r_packet(PKT_CONNECT, s->stream_id, (UBYTE)len, ior->a314_Buffer); + } + else if (s->pending_write != NULL) + { + struct A314_IORequest *ior = s->pending_write; + int len = ior->a314_Length; + + if (ior->a314_Request.io_Command == A314_WRITE) + { + append_a2r_packet(PKT_DATA, s->stream_id, (UBYTE)len, ior->a314_Buffer); + + ior->a314_Request.io_Error = A314_WRITE_OK; + ReplyMsg((struct Message *)ior); + + s->pending_write = NULL; + } + else // A314_EOS + { + append_a2r_packet(PKT_EOS, s->stream_id, 0, NULL); + + ior->a314_Request.io_Error = A314_EOS_OK; + ReplyMsg((struct Message *)ior); + + s->pending_write = NULL; + + s->flags |= SOCKET_SENT_EOS_TO_RPI; + + if (s->flags & SOCKET_SENT_EOS_TO_APP) + close_socket(s, FALSE); + } + } + else if (s->flags & SOCKET_SHOULD_SEND_RESET) + { + append_a2r_packet(PKT_RESET, s->stream_id, 0, NULL); + delete_socket(s); + } + else + { + debug_printf("SERIOUS ERROR: Was in send queue but has nothing to send\n"); + } + } +} + +static void handle_app_connect(struct A314_IORequest *ior, struct Socket *s) +{ + debug_printf("Received a CONNECT request from application\n"); + + if (s != NULL) + { + ior->a314_Request.io_Error = A314_CONNECT_SOCKET_IN_USE; + ReplyMsg((struct Message *)ior); + } + else if (ior->a314_Length + 3 > 255) + { + ior->a314_Request.io_Error = A314_CONNECT_RESET; + ReplyMsg((struct Message *)ior); + } + else + { + s = create_socket(ior->a314_Request.io_Message.mn_ReplyPort->mp_SigTask, ior->a314_Socket); + + s->pending_connect = ior; + s->flags = 0; + + int len = ior->a314_Length; + if (send_queue_head == NULL && room_in_a2r(len)) + { + append_a2r_packet(PKT_CONNECT, s->stream_id, (UBYTE)len, ior->a314_Buffer); + } + else + { + add_to_send_queue(s, len); + } + } +} + +static void handle_app_read(struct A314_IORequest *ior, struct Socket *s) +{ + debug_printf("Received a READ request from application\n"); + + if (s == NULL || (s->flags & SOCKET_CLOSED)) + { + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_READ_RESET; + ReplyMsg((struct Message *)ior); + } + else + { + if (s->pending_connect != NULL || s->pending_read != NULL) + { + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_READ_RESET; + ReplyMsg((struct Message *)ior); + + close_socket(s, TRUE); + } + else if (s->rq_head != NULL) + { + struct QueuedData *qd = s->rq_head; + int len = qd->length; + + if (ior->a314_Length < len) + { + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_READ_RESET; + ReplyMsg((struct Message *)ior); + + close_socket(s, TRUE); + } + else + { + s->rq_head = qd->next; + if (s->rq_head == NULL) + s->rq_tail = NULL; + + memcpy(ior->a314_Buffer, qd->data, len); + FreeMem(qd, sizeof(struct QueuedData) + len); + + ior->a314_Length = len; + ior->a314_Request.io_Error = A314_READ_OK; + ReplyMsg((struct Message *)ior); + } + } + else if (s->flags & SOCKET_RCVD_EOS_FROM_RPI) + { + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_READ_EOS; + ReplyMsg((struct Message *)ior); + + s->flags |= SOCKET_SENT_EOS_TO_APP; + + if (s->flags & SOCKET_SENT_EOS_TO_RPI) + close_socket(s, FALSE); + } + else + s->pending_read = ior; + } +} + +static void handle_app_write(struct A314_IORequest *ior, struct Socket *s) +{ + debug_printf("Received a WRITE request from application\n"); + + if (s == NULL || (s->flags & SOCKET_CLOSED)) + { + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_WRITE_RESET; + ReplyMsg((struct Message *)ior); + } + else + { + int len = ior->a314_Length; + if (s->pending_connect != NULL || s->pending_write != NULL || (s->flags & SOCKET_RCVD_EOS_FROM_APP) || len + 3 > 255) + { + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_WRITE_RESET; + ReplyMsg((struct Message *)ior); + + close_socket(s, TRUE); + } + else + { + if (send_queue_head == NULL && room_in_a2r(len)) + { + append_a2r_packet(PKT_DATA, s->stream_id, (UBYTE)len, ior->a314_Buffer); + + ior->a314_Request.io_Error = A314_WRITE_OK; + ReplyMsg((struct Message *)ior); + } + else + { + s->pending_write = ior; + add_to_send_queue(s, len); + } + } + } +} + +static void handle_app_eos(struct A314_IORequest *ior, struct Socket *s) +{ + debug_printf("Received an EOS request from application\n"); + + if (s == NULL || (s->flags & SOCKET_CLOSED)) + { + ior->a314_Request.io_Error = A314_EOS_RESET; + ReplyMsg((struct Message *)ior); + } + else + { + if (s->pending_connect != NULL || s->pending_write != NULL || (s->flags & SOCKET_RCVD_EOS_FROM_APP)) + { + ior->a314_Length = 0; + ior->a314_Request.io_Error = A314_EOS_RESET; + ReplyMsg((struct Message *)ior); + + close_socket(s, TRUE); + } + else + { + s->flags |= SOCKET_RCVD_EOS_FROM_APP; + + if (send_queue_head == NULL && room_in_a2r(0)) + { + append_a2r_packet(PKT_EOS, s->stream_id, 0, NULL); + + ior->a314_Request.io_Error = A314_EOS_OK; + ReplyMsg((struct Message *)ior); + + s->flags |= SOCKET_SENT_EOS_TO_RPI; + + if (s->flags & SOCKET_SENT_EOS_TO_APP) + close_socket(s, FALSE); + } + else + { + s->pending_write = ior; + add_to_send_queue(s, 0); + } + } + } +} + +static void handle_app_reset(struct A314_IORequest *ior, struct Socket *s) +{ + debug_printf("Received a RESET request from application\n"); + + if (s == NULL || (s->flags & SOCKET_CLOSED)) + { + ior->a314_Request.io_Error = A314_RESET_OK; + ReplyMsg((struct Message *)ior); + } + else + { + ior->a314_Request.io_Error = A314_RESET_OK; + ReplyMsg((struct Message *)ior); + + close_socket(s, TRUE); + } +} + +static void handle_app_request(struct A314_IORequest *ior) +{ + struct Socket *s = find_socket(ior->a314_Request.io_Message.mn_ReplyPort->mp_SigTask, ior->a314_Socket); + + switch (ior->a314_Request.io_Command) + { + case A314_CONNECT: + handle_app_connect(ior, s); + break; + case A314_READ: + handle_app_read(ior, s); + break; + case A314_WRITE: + handle_app_write(ior, s); + break; + case A314_EOS: + handle_app_eos(ior, s); + break; + case A314_RESET: + handle_app_reset(ior, s); + break; + default: + ior->a314_Request.io_Error = IOERR_NOCMD; + ReplyMsg((struct Message *)ior); + break; + } +} + +void task_main() +{ + while (TRUE) + { + debug_printf("Waiting for signal\n"); + + ULONG signal = Wait(SIGF_MSGPORT | SIGF_INT); + + UBYTE prev_a2r_tail = ca->a2r_tail; + UBYTE prev_r2a_head = ca->r2a_head; + + if (signal & SIGF_MSGPORT) + { + ca->a_enable = 0; + + struct Message *msg; + while (msg = GetMsg(&task_mp)) + handle_app_request((struct A314_IORequest *)msg); + } + + UBYTE a_enable = 0; + while (a_enable == 0) + { + handle_packets_received_r2a(); + handle_room_in_a2r(); + + UBYTE r_events = 0; + if (ca->a2r_tail != prev_a2r_tail) + r_events |= R_EVENT_A2R_TAIL; + if (ca->r2a_head != prev_r2a_head) + r_events |= R_EVENT_R2A_HEAD; + + UBYTE discard_value = ca->a_events; + + if (ca->r2a_head == ca->r2a_tail) + { + if (send_queue_head == NULL) + a_enable = A_EVENT_R2A_TAIL; + else if (!room_in_a2r(send_queue_head->send_queue_required_length)) + a_enable = A_EVENT_R2A_TAIL | A_EVENT_A2R_HEAD; + + if (a_enable != 0) + { + ca->a_enable = a_enable; + if (r_events != 0) + ca->r_events = r_events; + } + } + } + } + + // There is currently no way to unload a314.device. + + //debug_printf("Shutting down\n"); + + //RemIntServer(INTB_PORTS, &ports_interrupt); + //RemIntServer(INTB_VERTB, &vertb_interrupt); + //FreeMem(ca, sizeof(struct ComArea)); + + // Stack and task structure should be reclaimed. +} diff --git a/a314/a314device/build.bat b/a314/a314device/build.bat new file mode 100644 index 0000000..c4b605b --- /dev/null +++ b/a314/a314device/build.bat @@ -0,0 +1 @@ +vc romtag.asm a314driver.c device.c startup.c fix_mem_region.c sockets.c int_server.asm -O3 -nostdlib -o ../software-amiga/a314.device diff --git a/a314/a314device/debug.h b/a314/a314device/debug.h new file mode 100644 index 0000000..55a465b --- /dev/null +++ b/a314/a314device/debug.h @@ -0,0 +1,3 @@ +#define DEBUG 0 +//#define debug_printf(...) do { if (DEBUG) fprintf(stdout, __VA_ARGS__); } while (0) +#define debug_printf(...) diff --git a/a314/a314device/device.c b/a314/a314device/device.c new file mode 100644 index 0000000..a35a93d --- /dev/null +++ b/a314/a314device/device.c @@ -0,0 +1,125 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "a314.h" +#include "startup.h" +#include "fix_mem_region.h" + +char device_name[] = A314_NAME; +char id_string[] = A314_NAME " 1.1 (28 Nov 2020)"; + +struct ExecBase *SysBase; +BPTR saved_seg_list; +BOOL running = FALSE; + +static struct Library *init_device(__reg("a6") struct ExecBase *sys_base, __reg("a0") BPTR seg_list, __reg("d0") struct Library *dev) +{ + SysBase = *(struct ExecBase **)4; + saved_seg_list = seg_list; + + // We are being called from InitResident() in initializers.asm. + // MakeLibrary() was executed before we got here. + + dev->lib_Node.ln_Type = NT_DEVICE; + dev->lib_Node.ln_Name = device_name; + dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED; + dev->lib_Version = 1; + dev->lib_Revision = 0; + dev->lib_IdString = (APTR)id_string; + + // AddDevice() is executed after we return. + return dev; +} + +static BPTR expunge(__reg("a6") struct Library *dev) +{ + // There is currently no support for unloading a314.device. + + if (TRUE) //dev->lib_OpenCnt != 0) + { + dev->lib_Flags |= LIBF_DELEXP; + return 0; + } + + /* + BPTR seg_list = saved_seg_list; + Remove(&dev->lib_Node); + FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize); + return seg_list; + */ +} + +static void open(__reg("a6") struct Library *dev, __reg("a1") struct A314_IORequest *ior, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags) +{ + dev->lib_OpenCnt++; + + if (dev->lib_OpenCnt == 1 && !running) + { + if (!task_start()) + { + ior->a314_Request.io_Error = IOERR_OPENFAIL; + ior->a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + dev->lib_OpenCnt--; + return; + } + running = TRUE; + } + + ior->a314_Request.io_Error = 0; + ior->a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG; +} + +static BPTR close(__reg("a6") struct Library *dev, __reg("a1") struct A314_IORequest *ior) +{ + ior->a314_Request.io_Device = NULL; + ior->a314_Request.io_Unit = NULL; + + dev->lib_OpenCnt--; + + if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP)) + return expunge(dev); + + return 0; +} + +static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct A314_IORequest *ior) +{ + PutMsg(&task_mp, (struct Message *)ior); + ior->a314_Request.io_Flags &= ~IOF_QUICK; +} + +static ULONG abort_io(__reg("a6") struct Library *dev, __reg("a1") struct A314_IORequest *ior) +{ + // There is currently no support for aborting an IORequest. + return IOERR_NOCMD; +} + +static ULONG device_vectors[] = +{ + (ULONG)open, + (ULONG)close, + (ULONG)expunge, + 0, + (ULONG)begin_io, + (ULONG)abort_io, + (ULONG)translate_address_a314, + -1, +}; + +ULONG auto_init_tables[] = +{ + sizeof(struct Library), + (ULONG)device_vectors, + 0, + (ULONG)init_device, +}; diff --git a/a314/a314device/device.h b/a314/a314device/device.h new file mode 100644 index 0000000..dc9b19c --- /dev/null +++ b/a314/a314device/device.h @@ -0,0 +1,6 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +extern char device_name[]; +extern char id_string[]; diff --git a/a314/a314device/fix_mem_region.c b/a314/a314device/fix_mem_region.c new file mode 100644 index 0000000..fd40db2 --- /dev/null +++ b/a314/a314device/fix_mem_region.c @@ -0,0 +1,134 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include +#include +#include +#include +#include + +#include "a314.h" +#include "fix_mem_region.h" +#include "protocol.h" + +struct MemChunkList +{ + struct MemChunk *first; + struct MemChunk *last; + ULONG free; +}; + +void add_chunk(struct MemChunkList *l, struct MemChunk *mc) +{ + if (l->first == NULL) + l->first = mc; + else + l->last->mc_Next = mc; + l->last = mc; + l->free += mc->mc_Bytes; +} + +struct MemHeader *split_region(struct MemHeader *lower, ULONG split_at) +{ + struct MemHeader *upper = (struct MemHeader *)AllocMem(sizeof(struct MemHeader), MEMF_PUBLIC | MEMF_CLEAR); + + struct MemChunkList ll = {NULL, NULL, 0}; + struct MemChunkList ul = {NULL, NULL, 0}; + + struct MemChunk *mc = lower->mh_First; + + while (mc != NULL) + { + struct MemChunk *next_chunk = mc->mc_Next; + mc->mc_Next = NULL; + + ULONG start = (ULONG)mc; + ULONG end = start + mc->mc_Bytes; + + if (end <= split_at) + add_chunk(&ll, mc); + else if (split_at <= start) + add_chunk(&ul, mc); + else + { + mc->mc_Bytes = split_at - start; + add_chunk(&ll, mc); + + struct MemChunk *new_chunk = (struct MemChunk *)split_at; + new_chunk->mc_Next = NULL; + new_chunk->mc_Bytes = end - split_at; + add_chunk(&ul, new_chunk); + } + mc = next_chunk; + } + + upper->mh_Node.ln_Type = NT_MEMORY; + upper->mh_Node.ln_Pri = lower->mh_Node.ln_Pri; + upper->mh_Node.ln_Name = lower->mh_Node.ln_Name; // Use a custom name? + upper->mh_Attributes = lower->mh_Attributes; + + lower->mh_First = ll.first; + upper->mh_First = ul.first; + + upper->mh_Lower = (APTR)split_at; + upper->mh_Upper = lower->mh_Upper; + lower->mh_Upper = (APTR)split_at; + + lower->mh_Free = ll.free; + upper->mh_Free = ul.free; + + return upper; +} + +BOOL overlap(struct MemHeader *mh, ULONG lower, ULONG upper) +{ + return lower < (ULONG)(mh->mh_Upper) && (ULONG)(mh->mh_Lower) < upper; +} + +void mark_region_a314(ULONG address, ULONG size) +{ + struct List *memlist = &(SysBase->MemList); + + for (struct Node *node = memlist->lh_Head; node->ln_Succ != NULL; node = node->ln_Succ) + { + struct MemHeader *mh = (struct MemHeader *)node; + if (overlap(mh, address, address + size)) + { + if ((ULONG)mh->mh_Lower < address) + { + mh->mh_Attributes &= ~MEMF_A314; + mh = split_region(mh, address); + } + else + Remove((struct Node *)mh); + + if (address + size < (ULONG)mh->mh_Upper) + { + struct MemHeader *new_mh = split_region(mh, address + size); + new_mh->mh_Attributes &= ~MEMF_A314; + Enqueue(memlist, (struct Node *)new_mh); + } + + mh->mh_Attributes |= MEMF_A314; + Enqueue(memlist, (struct Node *)mh); + return; + } + } +} + +BOOL fix_memory() +{ + Forbid(); + mark_region_a314(ca->mem_base, ca->mem_size); + Permit(); + return TRUE; +} + +ULONG translate_address_a314(__reg("a0") void *address) +{ + ULONG offset = (ULONG)address - ca->mem_base; + if (offset < ca->mem_size) + return offset; + return -1; +} diff --git a/a314/a314device/fix_mem_region.h b/a314/a314device/fix_mem_region.h new file mode 100644 index 0000000..ea6fddf --- /dev/null +++ b/a314/a314device/fix_mem_region.h @@ -0,0 +1,8 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include + +extern ULONG translate_address_a314(__reg("a0") void *address); +extern BOOL fix_memory(); diff --git a/a314/a314device/int_server.asm b/a314/a314device/int_server.asm new file mode 100644 index 0000000..f43dd72 --- /dev/null +++ b/a314/a314device/int_server.asm @@ -0,0 +1,27 @@ +* +* Copyright 2020-2021 Niklas Ekström +* + + XDEF _IntServer + CODE + +SIGB_INT equ 14 +SIGF_INT equ (1 << SIGB_INT) + + ; a1 points to interrupt_data +_IntServer: move.l 4(a1),a5 ; interrupt_data.ca + + move.b 0(a5),d0 ; A_EVENTS + and.b 1(a5),d0 ; A_ENABLE + beq.s should_not_signal + + move.b #0,1(a5) + + move.l $4.w,a6 + move.l #SIGF_INT,d0 + move.l 0(a1),a1 ; interrupt_data.task + jsr -324(a6) ; Signal() + +should_not_signal: + moveq #0,d0 + rts diff --git a/a314/a314device/proto_a314.h b/a314/a314device/proto_a314.h new file mode 100644 index 0000000..e8512ef --- /dev/null +++ b/a314/a314device/proto_a314.h @@ -0,0 +1,13 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#ifndef PROTO_A314_H +#define PROTO_A314_H + +extern struct Library *A314Base; + +//ULONG __TranslateAddressA314(__reg("a6") void *, __reg("a0") void *)="\tjsr\t-42(a6)"; +#define TranslateAddressA314(address) (ULONG)address + +#endif diff --git a/a314/a314device/protocol.h b/a314/a314device/protocol.h new file mode 100644 index 0000000..3f06b15 --- /dev/null +++ b/a314/a314device/protocol.h @@ -0,0 +1,46 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include + +// Packet types that are sent across the physical channel. +#define PKT_DRIVER_STARTED 1 +#define PKT_DRIVER_SHUTTING_DOWN 2 +#define PKT_SETTINGS 3 +#define PKT_CONNECT 4 +#define PKT_CONNECT_RESPONSE 5 +#define PKT_DATA 6 +#define PKT_EOS 7 +#define PKT_RESET 8 + +// Events that are communicated via IRQ from Amiga to Raspberry. +#define R_EVENT_A2R_TAIL 1 +#define R_EVENT_R2A_HEAD 2 +#define R_EVENT_STARTED 4 + +// Events that are communicated from Raspberry to Amiga. +#define A_EVENT_R2A_TAIL 1 +#define A_EVENT_A2R_HEAD 2 + +// The communication area, used to create the physical channel. +struct ComArea +{ + volatile UBYTE a_events; + volatile UBYTE a_enable; + volatile UBYTE r_events; + volatile UBYTE r_enable; + + ULONG mem_base; + ULONG mem_size; + + volatile UBYTE a2r_tail; + volatile UBYTE r2a_head; + volatile UBYTE r2a_tail; + volatile UBYTE a2r_head; + + UBYTE a2r_buffer[256]; + UBYTE r2a_buffer[256]; +}; + +extern struct ComArea *ca; diff --git a/a314/a314device/romtag.asm b/a314/a314device/romtag.asm new file mode 100644 index 0000000..1d4fb49 --- /dev/null +++ b/a314/a314device/romtag.asm @@ -0,0 +1,27 @@ +* +* Copyright 2020-2021 Niklas Ekström +* + +RTC_MATCHWORD: equ $4afc +RTF_AUTOINIT: equ (1<<7) +NT_DEVICE: equ 3 +VERSION: equ 1 +PRIORITY: equ 0 + + section code,code + + moveq #-1,d0 + rts + +romtag: + dc.w RTC_MATCHWORD + dc.l romtag + dc.l endcode + dc.b RTF_AUTOINIT + dc.b VERSION + dc.b NT_DEVICE + dc.b PRIORITY + dc.l _device_name + dc.l _id_string + dc.l _auto_init_tables +endcode: diff --git a/a314/a314device/sockets.c b/a314/a314device/sockets.c new file mode 100644 index 0000000..89910b1 --- /dev/null +++ b/a314/a314device/sockets.c @@ -0,0 +1,119 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include + +#include "sockets.h" + +struct List active_sockets; + +struct Socket *send_queue_head = NULL; +struct Socket *send_queue_tail = NULL; + +static UBYTE next_stream_id = 1; + +extern void NewList(struct List *l); + +void init_sockets() +{ + NewList(&active_sockets); +} + +struct Socket *find_socket(void *sig_task, ULONG socket) +{ + for (struct Node *node = active_sockets.lh_Head; node->ln_Succ != NULL; node = node->ln_Succ) + { + struct Socket *s = (struct Socket *)node; + if (s->sig_task == sig_task && s->socket == socket) + return s; + } + return NULL; +} + +struct Socket *find_socket_by_stream_id(UBYTE stream_id) +{ + for (struct Node *node = active_sockets.lh_Head; node->ln_Succ != NULL; node = node->ln_Succ) + { + struct Socket *s = (struct Socket *)node; + if (s->stream_id == stream_id) + return s; + } + return NULL; +} + +static UBYTE allocate_stream_id() +{ + // Bug: If all stream ids are allocated then this loop won't terminate. + + while (1) + { + UBYTE stream_id = next_stream_id; + next_stream_id += 2; + if (find_socket_by_stream_id(stream_id) == NULL) + return stream_id; + } +} + +static void free_stream_id(UBYTE stream_id) +{ + // Currently do nothing. + // Could speed up allocate_stream_id using a bitmap? +} + +struct Socket *create_socket(struct Task *task, ULONG id) +{ + struct Socket *s = (struct Socket *)AllocMem(sizeof(struct Socket), MEMF_CLEAR); + s->sig_task = task; + s->socket = id; + s->stream_id = allocate_stream_id(); + AddTail(&active_sockets, (struct Node *)s); + return s; +} + +void delete_socket(struct Socket *s) +{ + Remove((struct Node *)s); + free_stream_id(s->stream_id); + FreeMem(s, sizeof(struct Socket)); +} + +void add_to_send_queue(struct Socket *s, UWORD required_length) +{ + s->send_queue_required_length = required_length; + s->next_in_send_queue = NULL; + + if (send_queue_head == NULL) + send_queue_head = s; + else + send_queue_tail->next_in_send_queue = s; + send_queue_tail = s; + + s->flags |= SOCKET_IN_SEND_QUEUE; +} + +void remove_from_send_queue(struct Socket *s) +{ + if (s->flags & SOCKET_IN_SEND_QUEUE) + { + if (send_queue_head == s) + { + send_queue_head = s->next_in_send_queue; + if (send_queue_head == NULL) + send_queue_tail = NULL; + } + else + { + struct Socket *curr = send_queue_head; + while (curr->next_in_send_queue != s) + curr = curr->next_in_send_queue; + + curr->next_in_send_queue = s->next_in_send_queue; + if (send_queue_tail == s) + send_queue_tail = curr; + } + + s->next_in_send_queue = NULL; + s->flags &= ~SOCKET_IN_SEND_QUEUE; + } +} diff --git a/a314/a314device/sockets.h b/a314/a314device/sockets.h new file mode 100644 index 0000000..efc5ffb --- /dev/null +++ b/a314/a314device/sockets.h @@ -0,0 +1,61 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include +#include + +// Used to store received data until application asks for it using a A314_READ. +struct QueuedData +{ + struct QueuedData *next; + UWORD length; + UBYTE data[]; +}; + +// Socket flags, these are bit masks, can have many of them. +#define SOCKET_RCVD_EOS_FROM_APP 0x0004 +#define SOCKET_RCVD_EOS_FROM_RPI 0x0008 +#define SOCKET_SENT_EOS_TO_APP 0x0010 +#define SOCKET_SENT_EOS_TO_RPI 0x0020 +#define SOCKET_CLOSED 0x0040 +#define SOCKET_SHOULD_SEND_RESET 0x0080 +#define SOCKET_IN_SEND_QUEUE 0x0100 + +struct Socket +{ + struct MinNode node; + + void *sig_task; + ULONG socket; + + UBYTE stream_id; + UBYTE pad1; + + UWORD flags; + + struct A314_IORequest *pending_connect; + struct A314_IORequest *pending_read; + struct A314_IORequest *pending_write; + + struct Socket *next_in_send_queue; + UWORD send_queue_required_length; + + // Data that is received on the stream, but the application didn't read yet. + struct QueuedData *rq_head; + struct QueuedData *rq_tail; +}; + +extern struct Socket *send_queue_head; +extern struct Socket *send_queue_tail; + +extern void init_sockets(); + +extern struct Socket *create_socket(struct Task *task, ULONG id); +extern void delete_socket(struct Socket *s); + +extern struct Socket *find_socket(void *sig_task, ULONG socket); +extern struct Socket *find_socket_by_stream_id(UBYTE stream_id); + +extern void add_to_send_queue(struct Socket *s, UWORD required_length); +extern void remove_from_send_queue(struct Socket *s); diff --git a/a314/a314device/startup.c b/a314/a314device/startup.c new file mode 100644 index 0000000..47ffb91 --- /dev/null +++ b/a314/a314device/startup.c @@ -0,0 +1,143 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "a314.h" +#include "device.h" +#include "protocol.h" +#include "startup.h" +#include "fix_mem_region.h" +#include "debug.h" + +#define A314_MANUFACTURER 0x07db +#define A314_PRODUCT 0xa3 + +#define TASK_PRIORITY 80 +#define TASK_STACK_SIZE 1024 + +struct ExpansionBase *ExpansionBase; +struct MsgPort task_mp; +struct Task *task; +struct ComArea *ca; + +struct InterruptData +{ + struct Task *task; + struct ComArea *ca; +}; + +struct InterruptData interrupt_data; +struct Interrupt ports_interrupt; + +extern void task_main(); +extern void init_sockets(); +extern void IntServer(); + +void NewList(struct List *l) +{ + l->lh_Head = (struct Node *)&(l->lh_Tail); + l->lh_Tail = NULL; + l->lh_TailPred = (struct Node *)&(l->lh_Head); +} + +static struct Task *create_task(char *name, long priority, char *initialPC, unsigned long stacksize) +{ + char *stack = AllocMem(stacksize, MEMF_CLEAR); + if (stack == NULL) + return NULL; + + struct Task *tc = AllocMem(sizeof(struct Task), MEMF_CLEAR | MEMF_PUBLIC); + if (tc == NULL) + { + FreeMem(stack, stacksize); + return NULL; + } + + tc->tc_Node.ln_Type = NT_TASK; + tc->tc_Node.ln_Pri = priority; + tc->tc_Node.ln_Name = name; + tc->tc_SPLower = (APTR)stack; + tc->tc_SPUpper = (APTR)(stack + stacksize); + tc->tc_SPReg = (APTR)(stack + stacksize); + + AddTask(tc, initialPC, 0); + return tc; +} + +static void init_message_port() +{ + task_mp.mp_Node.ln_Name = device_name; + task_mp.mp_Node.ln_Pri = 0; + task_mp.mp_Node.ln_Type = NT_MSGPORT; + task_mp.mp_Flags = PA_SIGNAL; + task_mp.mp_SigBit = SIGB_MSGPORT; + task_mp.mp_SigTask = task; + NewList(&(task_mp.mp_MsgList)); +} + +static void add_interrupt_handler() +{ + interrupt_data.task = task; + interrupt_data.ca = ca; + + ports_interrupt.is_Node.ln_Type = NT_INTERRUPT; + ports_interrupt.is_Node.ln_Pri = 0; + ports_interrupt.is_Node.ln_Name = device_name; + ports_interrupt.is_Data = (APTR)&interrupt_data; + ports_interrupt.is_Code = IntServer; + + AddIntServer(INTB_PORTS, &ports_interrupt); +} + +BOOL task_start() +{ + ExpansionBase = (struct ExpansionBase *)OpenLibrary(EXPANSIONNAME, 0); + if (!ExpansionBase) + return FALSE; + + struct ConfigDev *cd = FindConfigDev(NULL, A314_MANUFACTURER, A314_PRODUCT); + if (!cd) + { + CloseLibrary((struct Library *)ExpansionBase); + return FALSE; + } + + ca = (struct ComArea *)cd->cd_BoardAddr; + + if (!fix_memory()) + return FALSE; + + task = create_task(device_name, TASK_PRIORITY, (void *)task_main, TASK_STACK_SIZE); + if (task == NULL) + { + debug_printf("Unable to create task\n"); + return FALSE; + } + + init_message_port(); + init_sockets(); + + ca->a_enable = 0; + unsigned char discard_value = ca->a_events; + + ca->r_events = R_EVENT_STARTED; + + add_interrupt_handler(); + + ca->a_enable = A_EVENT_R2A_TAIL; + + return TRUE; +} diff --git a/a314/a314device/startup.h b/a314/a314device/startup.h new file mode 100644 index 0000000..85884f7 --- /dev/null +++ b/a314/a314device/startup.h @@ -0,0 +1,18 @@ +/* + * Copyright 2020-2021 Niklas Ekström + */ + +#include +#include +#include + +#define SIGB_INT 14 +#define SIGB_MSGPORT 15 + +#define SIGF_INT (1 << SIGB_INT) +#define SIGF_MSGPORT (1 << SIGB_MSGPORT) + +extern struct Task *task; +extern struct MsgPort task_mp; + +extern BOOL task_start(); diff --git a/a314/files_pi/.asoundrc b/a314/files_pi/.asoundrc new file mode 100644 index 0000000..283f999 --- /dev/null +++ b/a314/files_pi/.asoundrc @@ -0,0 +1,17 @@ +pcm.amiga { + type plug + slave { + pcm { + type file + format raw + file "/tmp/piaudio_pipe" + slave.pcm null + } + format S8 + rate 18000 + channels 2 + } + hint { + description "Play audio to Amiga using A314" + } +} diff --git a/a314/files_pi/a314d.conf b/a314/files_pi/a314d.conf new file mode 100644 index 0000000..ffe60df --- /dev/null +++ b/a314/files_pi/a314d.conf @@ -0,0 +1,7 @@ +a314fs python3 ./a314/files_pi/a314fs.py +picmd python3 ./a314/files_pi/picmd.py +piaudio python3 ./a314/files_pi/piaudio.py +remotewb python3 ./a314/files_pi/remotewb.py +remote-mouse python3 ./a314/files_pi/remote-mouse.py +videoplayer python3 ./a314/files_pi/videoplayer.py +ethernet python3 ./a314/files_pi/ethernet.py diff --git a/a314/files_pi/a314fs.conf b/a314/files_pi/a314fs.conf new file mode 100644 index 0000000..5addb41 --- /dev/null +++ b/a314/files_pi/a314fs.conf @@ -0,0 +1,8 @@ +{ + "devices": { + "PI0": { + "volume": "PiDisk", + "path": "./data/a314-shared" + } + } +} diff --git a/a314/files_pi/a314fs.py b/a314/files_pi/a314fs.py new file mode 100755 index 0000000..6cf45d2 --- /dev/null +++ b/a314/files_pi/a314fs.py @@ -0,0 +1,821 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2018-2021 Niklas Ekström + +import select +import sys +import socket +import time +import os +import struct +import glob +import logging +import json + +logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s') +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +try: + idx = sys.argv.index('-conf-file') + CONFIG_FILE_PATH = sys.argv[idx + 1] +except (ValueError, IndexError): + CONFIG_FILE_PATH = 'a314/files_pi/a314fs.conf' + +SHARED_DIRECTORY = 'data/a314shared' +METAFILE_EXTENSION = ':a314' + +with open(CONFIG_FILE_PATH, encoding='utf-8') as f: + cfg = json.load(f) + devs = cfg['devices'] + dev = devs['PI0'] + SHARED_DIRECTORY = dev['path'] + +MSG_REGISTER_REQ = 1 +MSG_REGISTER_RES = 2 +MSG_DEREGISTER_REQ = 3 +MSG_DEREGISTER_RES = 4 +MSG_READ_MEM_REQ = 5 +MSG_READ_MEM_RES = 6 +MSG_WRITE_MEM_REQ = 7 +MSG_WRITE_MEM_RES = 8 +MSG_CONNECT = 9 +MSG_CONNECT_RESPONSE = 10 +MSG_DATA = 11 +MSG_EOS = 12 +MSG_RESET = 13 + +def wait_for_msg(): + header = b'' + while len(header) < 9: + data = drv.recv(9 - len(header)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + header += data + (plen, stream_id, ptype) = struct.unpack('=IIB', header) + payload = b'' + while len(payload) < plen: + data = drv.recv(plen - len(payload)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + payload += data + return (stream_id, ptype, payload) + +def send_register_req(name): + m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name + drv.sendall(m) + +def send_read_mem_req(address, length): + m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length) + drv.sendall(m) + +def read_mem(address, length): + send_read_mem_req(address, length) + stream_id, ptype, payload = wait_for_msg() + if ptype != MSG_READ_MEM_RES: + logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + return payload + +def send_write_mem_req(address, data): + m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data + drv.sendall(m) + +def write_mem(address, data): + send_write_mem_req(address, data) + stream_id, ptype, payload = wait_for_msg() + if ptype != MSG_WRITE_MEM_RES: + logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + +def send_connect_response(stream_id, result): + m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result) + drv.sendall(m) + +def send_data(stream_id, data): + m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data + drv.sendall(m) + +def send_eos(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_EOS) + drv.sendall(m) + +def send_reset(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_RESET) + drv.sendall(m) + +ACTION_NIL = 0 +ACTION_GET_BLOCK = 2 +ACTION_SET_MAP = 4 +ACTION_DIE = 5 +ACTION_EVENT = 6 +ACTION_CURRENT_VOLUME = 7 +ACTION_LOCATE_OBJECT = 8 +ACTION_RENAME_DISK = 9 +ACTION_WRITE = ord('W') +ACTION_READ = ord('R') +ACTION_FREE_LOCK = 15 +ACTION_DELETE_OBJECT = 16 +ACTION_RENAME_OBJECT = 17 +ACTION_MORE_CACHE = 18 +ACTION_COPY_DIR = 19 +ACTION_WAIT_CHAR = 20 +ACTION_SET_PROTECT = 21 +ACTION_CREATE_DIR = 22 +ACTION_EXAMINE_OBJECT = 23 +ACTION_EXAMINE_NEXT = 24 +ACTION_DISK_INFO = 25 +ACTION_INFO = 26 +ACTION_FLUSH = 27 +ACTION_SET_COMMENT = 28 +ACTION_PARENT = 29 +ACTION_TIMER = 30 +ACTION_INHIBIT = 31 +ACTION_DISK_TYPE = 32 +ACTION_DISK_CHANGE = 33 +ACTION_SET_DATE = 34 +ACTION_SAME_LOCK = 40 +ACTION_SCREEN_MODE = 994 +ACTION_READ_RETURN = 1001 +ACTION_WRITE_RETURN = 1002 +ACTION_FINDUPDATE = 1004 +ACTION_FINDINPUT = 1005 +ACTION_FINDOUTPUT = 1006 +ACTION_END = 1007 +ACTION_SEEK = 1008 +ACTION_TRUNCATE = 1022 +ACTION_WRITE_PROTECT = 1023 +ACTION_EXAMINE_FH = 1034 +ACTION_UNSUPPORTED = 65535 + +ERROR_NO_FREE_STORE = 103 +ERROR_TASK_TABLE_FULL = 105 +ERROR_LINE_TOO_LONG = 120 +ERROR_FILE_NOT_OBJECT = 121 +ERROR_INVALID_RESIDENT_LIBRARY = 122 +ERROR_NO_DEFAULT_DIR = 201 +ERROR_OBJECT_IN_USE = 202 +ERROR_OBJECT_EXISTS = 203 +ERROR_DIR_NOT_FOUND = 204 +ERROR_OBJECT_NOT_FOUND = 205 +ERROR_BAD_STREAM_NAME = 206 +ERROR_OBJECT_TOO_LARGE = 207 +ERROR_ACTION_NOT_KNOWN = 209 +ERROR_INVALID_COMPONENT_NAME = 210 +ERROR_INVALID_LOCK = 211 +ERROR_OBJECT_WRONG_TYPE = 212 +ERROR_DISK_NOT_VALIDATED = 213 +ERROR_DISK_WRITE_PROTECTED = 214 +ERROR_RENAME_ACROSS_DEVICES = 215 +ERROR_DIRECTORY_NOT_EMPTY = 216 +ERROR_TOO_MANY_LEVELS = 217 +ERROR_DEVICE_NOT_MOUNTED = 218 +ERROR_SEEK_ERROR = 219 +ERROR_COMMENT_TOO_BIG = 220 +ERROR_DISK_FULL = 221 +ERROR_DELETE_PROTECTED = 222 +ERROR_WRITE_PROTECTED = 223 +ERROR_READ_PROTECTED = 224 +ERROR_NOT_A_DOS_DISK = 225 +ERROR_NO_DISK = 226 +ERROR_NO_MORE_ENTRIES = 232 + +SHARED_LOCK = -2 +EXCLUSIVE_LOCK = -1 + +LOCK_DIFFERENT = -1 +LOCK_SAME = 0 +LOCK_SAME_VOLUME = 1 + +MODE_OLDFILE = 1005 # Open existing file read/write positioned at beginning of file. +MODE_NEWFILE = 1006 # Open freshly created file (delete old file) read/write, exclusive lock. +MODE_READWRITE = 1004 # Open old file w/shared lock, creates file if doesn't exist. + +OFFSET_BEGINNING = -1 # Relative to Begining Of File. +OFFSET_CURRENT = 0 # Relative to Current file position. +OFFSET_END = 1 # relative to End Of File. + +ST_ROOT = 1 +ST_USERDIR = 2 +ST_SOFTLINK = 3 # looks like dir, but may point to a file! +ST_LINKDIR = 4 # hard link to dir +ST_FILE = -3 # must be negative for FIB! +ST_LINKFILE = -4 # hard link to file +ST_PIPEFILE = -5 + +current_stream_id = 0 + +class ObjectLock(object): + def __init__(self, key, mode, path): + self.key = key + self.mode = mode + self.path = path + self.entry_it = None + +locks = {} + +next_key = 1 + +def get_key(): + global next_key + key = next_key + next_key = 1 if next_key == 0x7fffffff else (next_key + 1) + while key in locks: + key += 1 + return key + +def find_path(key, name): + i = name.find(':') + if i != -1: + vol = name[:i].lower() + if vol == '' or vol == 'pi0' or vol == 'pidisk': + key = 0 + name = name[i + 1:] + + if key == 0: + cp = () + else: + cp = locks[key].path + + while name: + i = name.find('/') + if i == -1: + comp = name + name = '' + else: + comp = name[:i] + name = name[i + 1:] + + if len(comp) == 0: + if len(cp) == 0: + return None + cp = cp[:-1] + else: + p = '.' if len(cp) == 0 else '/'.join(cp) + entries = os.listdir(p) + found = False + for e in entries: + if comp.lower() == e.lower(): + cp = cp + (e,) + found = True + break + if not found: + if len(name) == 0: + cp = cp + (comp,) + else: + return None + + return cp + +def read_metadata(path): + protection = 0 + comment = '' + + if not os.path.isfile(path + METAFILE_EXTENSION): + return (protection, comment) + + try: + f = open(path + METAFILE_EXTENSION, 'r') + for line in f: + if line[0] == 'p': + try: + protection = int(line[1:].strip()) + except ValueError: + pass + elif line[0] == 'c': + comment = line[1:].strip()[:79] + f.close() + except FileNotFoundError: + pass + return (protection, comment) + +def write_metadata(path, protection=None, comment=None): + p, c = read_metadata(path) + + if protection == None: + protection = p + if comment == None: + comment = c + + if (p, c) == (protection, comment): + return True + + try: + f = open(path + METAFILE_EXTENSION, 'w') + f.write('p' + str(protection) + '\n') + f.write('c' + comment + '\n') + f.close() + except FileNotFoundError as e: + logger.warning('Failed to write metadata for file %s: %s', path, e) + return False + return True + +def process_locate_object(key, mode, name): + logger.debug('ACTION_LOCATE_OBJECT, key: %s, mode: %s, name: %s', key, mode, name) + + cp = find_path(key, name) + + if cp is None or not (len(cp) == 0 or os.path.exists('/'.join(cp))): + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + # TODO: Must check if there is already a lock for this path, + # and if so, if the locks are compatible. + + key = get_key() + locks[key] = ObjectLock(key, mode, cp) + return struct.pack('>HHI', 1, 0, key) + +def process_free_lock(key): + logger.debug('ACTION_FREE_LOCK, key: %s', key) + if key in locks: + del locks[key] + return struct.pack('>HH', 1, 0) + +def process_copy_dir(prev_key): + logger.debug('ACTION_COPY_DIR, prev_key: %s', prev_key) + ol = locks[prev_key] + key = get_key() + locks[key] = ObjectLock(key, ol.mode, ol.path) + return struct.pack('>HHI', 1, 0, key) + +def process_parent(prev_key): + logger.debug('ACTION_PARENT, prev_key: %s', prev_key) + ol = locks[prev_key] + if len(ol.path) == 0: + key = 0 + else: + key = get_key() + locks[key] = ObjectLock(key, SHARED_LOCK, ol.path[:-1]) + return struct.pack('>HHI', 1, 0, key) + +def mtime_to_dmt(mtime): + mtime = int(mtime) + days = mtime // 86400 + left = mtime - days * 86400 + mins = left // 60 + secs = left - mins * 60 + ticks = secs * 50 + days -= 2922 # Days between 1970-01-01 and 1978-01-01 + days = max(0, days) # If days are before Amiga epoc + return (days, mins, ticks) + +def process_examine_object(key): + logger.debug('ACTION_EXAMINE_OBJECT, key: %s', key) + ol = locks[key] + + if len(ol.path) == 0: + fn = 'PiDisk' + path = '.' + else: + fn = ol.path[-1] + path = '/'.join(ol.path) + + days, mins, ticks = mtime_to_dmt(os.path.getmtime(path)) + protection, comment = read_metadata(path) + + if os.path.isfile(path): + size = os.path.getsize(path) + type_ = ST_FILE + else: + size = 0 + type_ = ST_USERDIR + ol.entry_it = os.scandir(path) + + size = min(size, 2 ** 31 - 1) + fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore') + comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore') + return struct.pack('>HHHhIIIII', 1, 0, 666, type_, size, protection, days, mins, ticks) + fn + comment + +def process_examine_next(key, disk_key): + logger.debug('ACTION_EXAMINE_NEXT, key: %s, disk_key: %s', key, disk_key) + ol = locks[key] + + if len(ol.path) == 0: + path = '.' + else: + path = '/'.join(ol.path) + + if not os.path.isdir(path): + return struct.pack('>HH', 0, ERROR_OBJECT_WRONG_TYPE) + + disk_key += 1 + + entry = next(ol.entry_it, None) + while entry and entry.name.endswith(METAFILE_EXTENSION): + entry = next(ol.entry_it, None) + + if not entry: + return struct.pack('>HH', 0, ERROR_NO_MORE_ENTRIES) + + fn = entry.name + path = ('/'.join(ol.path + (fn,))) + + days, mins, ticks = mtime_to_dmt(entry.stat().st_mtime) + protection, comment = read_metadata(path) + + if os.path.isfile(path): + size = os.path.getsize(path) + type_ = ST_FILE + else: + size = 0 + type_ = ST_USERDIR + + size = min(size, 2 ** 31 - 1) + fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore') + comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore') + return struct.pack('>HHHhIIIII', 1, 0, disk_key, type_, size, protection, days, mins, ticks) + fn + comment + +def process_examine_fh(arg1): + logger.debug('ACTION_EXAMINE_FH, arg1: %s', arg1) + + fn = open_file_handles[arg1].f.name + path = os.path.realpath(fn) + days, mins, ticks = mtime_to_dmt(os.path.getmtime(path)) + protection, comment = read_metadata(path) + + if os.path.isfile(path): + size = os.path.getsize(path) + type_ = ST_FILE + else: + size = 0 + type_ = ST_USERDIR + + size = min(size, 2 ** 31 - 1) + fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore') + comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore') + return struct.pack('>HHHhIIIII', 1, 0, 666, type_, size, protection, days, mins, ticks) + fn + comment + + +next_fp = 1 + +open_file_handles = {} + +def get_file_ptr(): + global next_fp + fp = next_fp + next_fp = 1 if next_fp == 0x7fffffff else next_fp + 1 + while fp in open_file_handles: + fp += 1 + return fp + +class OpenFileHandle(object): + def __init__(self, fp, f, p): + self.fp = fp + self.f = f + self.p = p + +def process_findxxx(mode, key, name): + if mode == ACTION_FINDINPUT: + logger.debug('ACTION_FINDINPUT, key: %s, name: %s', key, name) + elif mode == ACTION_FINDOUTPUT: + logger.debug('ACTION_FINDOUTPUT, key: %s, name: %s', key, name) + elif mode == ACTION_FINDUPDATE: + logger.debug('ACTION_FINDUPDATE, key: %s, name: %s', key, name) + + cp = find_path(key, name) + if cp is None: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + path = '/'.join(cp) + if len(cp) == 0 or os.path.isdir(path): + return struct.pack('>HH', 0, ERROR_OBJECT_WRONG_TYPE) + + # TODO: Must check if there already exists a non-compatible lock for this path. + + # TODO: This must be handled better. Especially error reporting. + + protection, _ = read_metadata(path) + try: + if mode == MODE_OLDFILE: + f = open(path, 'r+b') + elif mode == MODE_READWRITE: + f = open(path, 'r+b') + if protection & 16: + protection = protection & 0b11101111 + write_metadata(path, protection=protection) + elif mode == MODE_NEWFILE: + if protection & 0x1: + return struct.pack('>HH', 0, ERROR_DELETE_PROTECTED) + elif protection & 0x4: + return struct.pack('>HH', 0, ERROR_WRITE_PROTECTED) + f = open(path, 'w+b') + if protection & 16: + protection = protection & 0b11101111 + write_metadata(path, protection=protection) + except IOError: + if mode == MODE_READWRITE: + try: + f = open(path, 'w+b') + if protection & 16: + protection = protection & 0b11101111 + write_metadata(path, protection=protection) + except IOError: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + else: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + fp = get_file_ptr() + ofh = OpenFileHandle(fp, f, protection) + open_file_handles[fp] = ofh + + return struct.pack('>HHI', 1, 0, fp) + +def process_read(arg1, address, length): + logger.debug('ACTION_READ, arg1: %s, address: %s, length: %s', arg1, address, length) + protection = open_file_handles[arg1].p + if protection & 0x8: + return struct.pack('>HH', 0, ERROR_READ_PROTECTED) + f = open_file_handles[arg1].f + data = f.read(length) + if len(data) != 0: + write_mem(address, data) + return struct.pack('>HHI', 1, 0, len(data)) + +def process_write(arg1, address, length): + logger.debug('ACTION_WRITE, arg1: %s, address: %s, length: %s', arg1, address, length) + protection = open_file_handles[arg1].p + if protection & 0x4: + return struct.pack('>HH', 0, ERROR_WRITE_PROTECTED) + data = read_mem(address, length) + f = open_file_handles[arg1].f + f.seek(0, 1) + try: + f.write(data) + except IOError: + return struct.pack('>HH', 0, ERROR_DISK_FULL) + return struct.pack('>HHI', 1, 0, length) + +def process_seek(arg1, new_pos, mode): + logger.debug('ACTION_SEEK, arg1: %s, new_pos: %s, mode: %s', arg1, new_pos, mode) + + f = open_file_handles[arg1].f + old_pos = f.tell() + + from_what = 0 + if mode == OFFSET_CURRENT: + from_what = 1 + elif mode == OFFSET_END: + from_what = 2 + + f.seek(new_pos, from_what) + + return struct.pack('>HHi', 1, 0, old_pos) + +def process_end(arg1): + logger.debug('ACTION_END, arg1: %s', arg1) + + if arg1 in open_file_handles: + f = open_file_handles.pop(arg1).f + f.close() + + return struct.pack('>HH', 1, 0) + +def process_delete_object(key, name): + logger.debug('ACTION_DELETE_OBJECT, key: %s, name: %s', key, name) + + cp = find_path(key, name) + if cp is None or len(cp) == 0: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + path = '/'.join(cp) + is_dir = os.path.isdir(path) + + protection, _ = read_metadata(path) + if protection & 0x1: + return struct.pack('>HH', 0, ERROR_DELETE_PROTECTED) + + try: + if is_dir: + os.rmdir(path) + else: + os.remove(path) + if os.path.isfile(path + METAFILE_EXTENSION): + os.remove(path + METAFILE_EXTENSION) + except: + if is_dir: + return struct.pack('>HH', 0, ERROR_DIRECTORY_NOT_EMPTY) + else: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + return struct.pack('>HH', 1, 0) + +def process_rename_object(key, name, target_dir, new_name): + logger.debug('ACTION_RENAME_OBJECT, key: %s, name: %s, target_dir: %s, new_name: %s', key, name, target_dir, new_name) + + cp1 = find_path(key, name) + if cp1 is None or len(cp1) == 0: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + from_path = '/'.join(cp1) + if not os.path.exists(from_path): + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + cp2 = find_path(target_dir, new_name) + if cp2 is None or len(cp2) == 0: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + to_path = '/'.join(cp2) + + if from_path == to_path: + return struct.pack('>HH', 1, 0) + + if os.path.exists(to_path): + return struct.pack('>HH', 0, ERROR_OBJECT_EXISTS) + + try: + os.rename(from_path, to_path) + if os.path.isfile(from_path + METAFILE_EXTENSION): + os.rename(from_path + METAFILE_EXTENSION, to_path + METAFILE_EXTENSION) + except: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + return struct.pack('>HH', 1, 0) + +def process_create_dir(key, name): + logger.debug('ACTION_CREATE_DIR, key: %s, name: %s', key, name) + + cp = find_path(key, name) + if cp is None or len(cp) == 0: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + try: + path = '/'.join(cp) + os.makedirs(path) + except: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + key = get_key() + locks[key] = ObjectLock(key, SHARED_LOCK, cp) + return struct.pack('>HHI', 1, 0, key) + +def process_set_protect(key, name, mask): + logger.debug('ACTION_SET_PROTECT, key: %s, name: %s, mask: %s', key, name, mask) + + cp = find_path(key, name) + if cp is None or len(cp) == 0: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + path = '/'.join(cp) + if write_metadata(path, protection=mask): + return struct.pack('>HH', 1, 0) + else: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + +def process_set_comment(key, name, comment): + logger.debug('ACTION_SET_COMMENT, key: %s, name: %s, comment: %s', key, name, comment) + + if len(comment) > 79: + return struct.pack('>HH', 0, ERROR_COMMENT_TOO_BIG) + + cp = find_path(key, name) + if cp is None or len(cp) == 0: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + + path = '/'.join(cp) + if write_metadata(path, comment=comment): + return struct.pack('>HH', 1, 0) + else: + return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND) + +def process_same_lock(key1, key2): + logger.debug('ACTION_SAME_LOCK, key1: %s key2: %s', key1, key2) + + if not (key1 in locks and key2 in locks): + return struct.pack('>HH', 0, LOCK_DIFFERENT) + elif locks[key1].path == locks[key2].path: + return struct.pack('>HH', 1, LOCK_SAME) + else: + return struct.pack('>HH', 0, LOCK_SAME_VOLUME) + +def process_request(req): + #logger.debug('len(req): %s, req: %s', len(req), list(req)) + + (rtype,) = struct.unpack('>H', req[:2]) + + if rtype == ACTION_LOCATE_OBJECT: + key, mode, nlen = struct.unpack('>IHB', req[2:9]) + name = req[9:9+nlen].decode('latin-1') + return process_locate_object(key, mode, name) + elif rtype == ACTION_FREE_LOCK: + (key,) = struct.unpack('>I', req[2:6]) + return process_free_lock(key) + elif rtype == ACTION_COPY_DIR: + (key,) = struct.unpack('>I', req[2:6]) + return process_copy_dir(key) + elif rtype == ACTION_PARENT: + (key,) = struct.unpack('>I', req[2:6]) + return process_parent(key) + elif rtype == ACTION_EXAMINE_OBJECT: + (key,) = struct.unpack('>I', req[2:6]) + return process_examine_object(key) + elif rtype == ACTION_EXAMINE_NEXT: + key, disk_key = struct.unpack('>IH', req[2:8]) + return process_examine_next(key, disk_key) + elif rtype == ACTION_EXAMINE_FH: + (arg1,) = struct.unpack('>I', req[2:6]) + return process_examine_fh(arg1) + elif rtype == ACTION_FINDINPUT or rtype == ACTION_FINDOUTPUT or rtype == ACTION_FINDUPDATE: + key, nlen = struct.unpack('>IB', req[2:7]) + name = req[7:7+nlen].decode('latin-1') + return process_findxxx(rtype, key, name) + elif rtype == ACTION_READ: + arg1, address, length = struct.unpack('>III', req[2:14]) + return process_read(arg1, address, length) + elif rtype == ACTION_WRITE: + arg1, address, length = struct.unpack('>III', req[2:14]) + return process_write(arg1, address, length) + elif rtype == ACTION_SEEK: + arg1, new_pos, mode = struct.unpack('>Iii', req[2:14]) + return process_seek(arg1, new_pos, mode) + elif rtype == ACTION_END: + (arg1,) = struct.unpack('>I', req[2:6]) + return process_end(arg1) + elif rtype == ACTION_DELETE_OBJECT: + key, nlen = struct.unpack('>IB', req[2:7]) + name = req[7:7+nlen].decode('latin-1') + return process_delete_object(key, name) + elif rtype == ACTION_RENAME_OBJECT: + key, target_dir, nlen, nnlen = struct.unpack('>IIBB', req[2:12]) + name = req[12:12+nlen].decode('latin-1') + new_name = req[12+nlen:12+nlen+nnlen].decode('latin-1') + return process_rename_object(key, name, target_dir, new_name) + elif rtype == ACTION_CREATE_DIR: + key, nlen = struct.unpack('>IB', req[2:7]) + name = req[7:7+nlen].decode('latin-1') + return process_create_dir(key, name) + elif rtype == ACTION_SET_PROTECT: + key, mask, nlen = struct.unpack('>IIB', req[2:11]) + name = req[11:11+nlen].decode('latin-1') + return process_set_protect(key, name, mask) + elif rtype == ACTION_SET_COMMENT: + key, nlen, clen = struct.unpack('>IBB', req[2:8]) + name = req[8:8+nlen].decode('latin-1') + comment = req[8+nlen:8+nlen+clen].decode('latin-1') + return process_set_comment(key, name, comment) + elif rtype == ACTION_SAME_LOCK: + key1, key2 = struct.unpack('>II', req[2:10]) + return process_same_lock(key1, key2) + elif rtype == ACTION_UNSUPPORTED: + (dp_Type,) = struct.unpack('>H', req[2:4]) + logger.warning('Unsupported action %d (Amiga/a314fs)', dp_Type) + return struct.pack('>HH', 0, ERROR_ACTION_NOT_KNOWN) + else: + logger.warning('Unsupported action %d (a314d/a314fs)', rtype) + return struct.pack('>HH', 0, ERROR_ACTION_NOT_KNOWN) + +done = False + +try: + idx = sys.argv.index('-ondemand') +except ValueError: + idx = -1 + +if idx != -1: + fd = int(sys.argv[idx + 1]) + drv = socket.socket(fileno=fd) +else: + drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + drv.connect(('localhost', 7110)) + + send_register_req(b'a314fs') + stream_id, ptype, payload = wait_for_msg() + if payload[0] != 1: + logger.error('Unable to register service a314fs, shutting down') + drv.close() + done = True + +if not done: + os.chdir(SHARED_DIRECTORY) + logger.info('a314fs is running, shared directory: %s', SHARED_DIRECTORY) + +while not done: + stream_id, ptype, payload = wait_for_msg() + + if ptype == MSG_CONNECT: + if payload == b'a314fs': + if current_stream_id is not None: + send_reset(current_stream_id) + current_stream_id = stream_id + send_connect_response(stream_id, 0) + else: + send_connect_response(stream_id, 3) + elif ptype == MSG_DATA: + address, length = struct.unpack('>II', payload) + #logger.debug('address: %s, length: %s', address, length) + req = read_mem(address + 2, length - 2) + res = process_request(req) + write_mem(address + 2, res) + #write_mem(address, b'\xff\xff') + send_data(stream_id, b'\xff\xff') + elif ptype == MSG_EOS: + if stream_id == current_stream_id: + logger.debug('Got EOS, stream closed') + send_eos(stream_id) + current_stream_id = None + elif ptype == MSG_RESET: + if stream_id == current_stream_id: + logger.debug('Got RESET, stream closed') + current_stream_id = None diff --git a/a314/files_pi/eth-config-pi/rc.local b/a314/files_pi/eth-config-pi/rc.local new file mode 100644 index 0000000..f7581f3 --- /dev/null +++ b/a314/files_pi/eth-config-pi/rc.local @@ -0,0 +1,3 @@ +iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE +iptables -A FORWARD -i wlan0 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT +iptables -A FORWARD -i tap0 -o wlan0 -j ACCEPT diff --git a/a314/files_pi/eth-config-pi/tap0 b/a314/files_pi/eth-config-pi/tap0 new file mode 100644 index 0000000..372406d --- /dev/null +++ b/a314/files_pi/eth-config-pi/tap0 @@ -0,0 +1,9 @@ +auto tap0 + +iface tap0 inet static + address 192.168.2.1 + netmask 255.255.255.0 + broadcast 192.168.2.255 + pre-up ip tuntap add tap0 mode tap user pi group pi + up ip link set dev tap0 up + post-down ip link del dev tap0 diff --git a/a314/files_pi/ethernet.py b/a314/files_pi/ethernet.py new file mode 100755 index 0000000..028875f --- /dev/null +++ b/a314/files_pi/ethernet.py @@ -0,0 +1,264 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Niklas Ekström + +import logging +import os +import pytun +import select +import socket +import struct +import sys +import time + +logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s') +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +MSG_REGISTER_REQ = 1 +MSG_REGISTER_RES = 2 +MSG_DEREGISTER_REQ = 3 +MSG_DEREGISTER_RES = 4 +MSG_READ_MEM_REQ = 5 +MSG_READ_MEM_RES = 6 +MSG_WRITE_MEM_REQ = 7 +MSG_WRITE_MEM_RES = 8 +MSG_CONNECT = 9 +MSG_CONNECT_RESPONSE = 10 +MSG_DATA = 11 +MSG_EOS = 12 +MSG_RESET = 13 + +def wait_for_msg(): + header = b'' + while len(header) < 9: + data = drv.recv(9 - len(header)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + header += data + (plen, stream_id, ptype) = struct.unpack('=IIB', header) + payload = b'' + while len(payload) < plen: + data = drv.recv(plen - len(payload)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + payload += data + return (stream_id, ptype, payload) + +def send_register_req(name): + m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name + drv.sendall(m) + +def send_read_mem_req(address, length): + m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length) + drv.sendall(m) + +def read_mem(address, length): + send_read_mem_req(address, length) + _, ptype, payload = wait_for_msg() + if ptype != MSG_READ_MEM_RES: + logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + return payload + +def send_write_mem_req(address, data): + m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data + drv.sendall(m) + +def write_mem(address, data): + send_write_mem_req(address, data) + _, ptype, _ = wait_for_msg() + if ptype != MSG_WRITE_MEM_RES: + logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + +def send_connect_response(stream_id, result): + m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result) + drv.sendall(m) + +def send_data(stream_id, data): + m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data + drv.sendall(m) + +def send_eos(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_EOS) + drv.sendall(m) + +def send_reset(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_RESET) + drv.sendall(m) + +### A314 communication routines above. Actual driver below. + +current_stream_id = None +done = False +rbuf = b'' + +DEV_NAME = 'tap0' +SERVICE_NAME = b'ethernet' + +READ_FRAME_REQ = 1 +WRITE_FRAME_REQ = 2 +READ_FRAME_RES = 3 +WRITE_FRAME_RES = 4 + +mem_read_queue = [] +mem_write_queue = [] + +# Can buffer as many frames as fit in memory. +# Maybe should have a limit on the number of buffers? +waiting_read_reqs = [] +buffered_frames = [] + +DROP_START_SECS = 15.0 + +def process_tap_frame(frame): + if current_stream_id is None: + return + + global drop_start + if drop_start: + if time.time() < start_time + DROP_START_SECS: + return + drop_start = False + + if waiting_read_reqs: + stream_id, address, length = waiting_read_reqs.pop(0) + + if length < len(frame): + logger.error('Fatal error, read frame from TAP larger than buffer') + + mem_write_queue.append((stream_id, address, len(frame))) + send_write_mem_req(address, frame) + else: + buffered_frames.append(frame) + +def process_stream_data(stream_id, data): + address, length, kind = struct.unpack('>IHH', data) + if kind == WRITE_FRAME_REQ: + mem_read_queue.append((stream_id, address, length)) + send_read_mem_req(address, length) + elif kind == READ_FRAME_REQ: + if buffered_frames: + frame = buffered_frames.pop(0) + + if length < len(frame): + logger.error('Fatal error, read frame from TAP larger than buffer') + + mem_write_queue.append((stream_id, address, len(frame))) + send_write_mem_req(address, frame) + else: + waiting_read_reqs.append((stream_id, address, length)) + +def process_read_mem_res(frame): + tap.write(frame) + + stream_id, address, length = mem_read_queue.pop(0) + if stream_id == current_stream_id: + send_data(stream_id, struct.pack('>IHH', address, length, WRITE_FRAME_RES)) + +def process_write_mem_res(): + stream_id, address, length = mem_write_queue.pop(0) + if stream_id == current_stream_id: + send_data(stream_id, struct.pack('>IHH', address, length, READ_FRAME_RES)) + +def process_drv_msg(stream_id, ptype, payload): + global current_stream_id + + if ptype == MSG_CONNECT: + if payload == SERVICE_NAME and current_stream_id is None: + logger.info('Amiga connected') + current_stream_id = stream_id + send_connect_response(stream_id, 0) + else: + send_connect_response(stream_id, 3) + elif ptype == MSG_READ_MEM_RES: + process_read_mem_res(payload) + elif ptype == MSG_WRITE_MEM_RES: + process_write_mem_res() + elif current_stream_id == stream_id: + if ptype == MSG_DATA: + process_stream_data(stream_id, payload) + elif ptype == MSG_EOS: + # EOS is not used. + pass + elif ptype == MSG_RESET: + current_stream_id = None + waiting_read_reqs.clear() + buffered_frames.clear() + logger.info('Amiga disconnected') + +try: + idx = sys.argv.index('-ondemand') +except ValueError: + idx = -1 + +if idx != -1: + fd = int(sys.argv[idx + 1]) + drv = socket.socket(fileno=fd) +else: + drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + drv.connect(('localhost', 7110)) + drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + send_register_req(SERVICE_NAME) + _, _, payload = wait_for_msg() + if payload[0] != 1: + logger.error('Unable to register ethernet with driver, shutting down') + drv.close() + done = True + +if not done: + try: + tap = pytun.TunTapDevice(name=DEV_NAME, flags=pytun.IFF_TAP | pytun.IFF_NO_PI) + except: + logger.error('Unable to open tap device at ' + DEV_NAME) + done = True + + start_time = time.time() + drop_start = True + +if not done: + logger.info('Ethernet service is running') + +while not done: + try: + rl, _, _ = select.select([drv, tap], [], [], 10.0) + except KeyboardInterrupt: + rl = [] + if current_stream_id is not None: + send_reset(current_stream_id) + drv.close() + done = True + + if drv in rl: + buf = drv.recv(1600) + if not buf: + if current_stream_id is not None: + send_reset(current_stream_id) + drv.close() + done = True + else: + rbuf += buf + while True: + if len(rbuf) < 9: + break + + (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9]) + if len(rbuf) < 9 + plen: + break + + payload = rbuf[9:9+plen] + rbuf = rbuf[9+plen:] + + process_drv_msg(stream_id, ptype, payload) + + if tap in rl: + frame = tap.read(1600) + process_tap_frame(frame) + +tap.close() +logger.info('Ethernet service stopped') diff --git a/a314/files_pi/piaudio.py b/a314/files_pi/piaudio.py new file mode 100755 index 0000000..113a4ce --- /dev/null +++ b/a314/files_pi/piaudio.py @@ -0,0 +1,252 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Niklas Ekström + +import fcntl +import logging +import os +import select +import socket +import struct +import sys + +fcntl.F_SETPIPE_SZ = 1031 + +logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s') +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +MSG_REGISTER_REQ = 1 +MSG_REGISTER_RES = 2 +MSG_DEREGISTER_REQ = 3 +MSG_DEREGISTER_RES = 4 +MSG_READ_MEM_REQ = 5 +MSG_READ_MEM_RES = 6 +MSG_WRITE_MEM_REQ = 7 +MSG_WRITE_MEM_RES = 8 +MSG_CONNECT = 9 +MSG_CONNECT_RESPONSE = 10 +MSG_DATA = 11 +MSG_EOS = 12 +MSG_RESET = 13 + +def wait_for_msg(): + header = b'' + while len(header) < 9: + data = drv.recv(9 - len(header)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + header += data + (plen, stream_id, ptype) = struct.unpack('=IIB', header) + payload = b'' + while len(payload) < plen: + data = drv.recv(plen - len(payload)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + payload += data + return (stream_id, ptype, payload) + +def send_register_req(name): + m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name + drv.sendall(m) + +def send_read_mem_req(address, length): + m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length) + drv.sendall(m) + +def read_mem(address, length): + send_read_mem_req(address, length) + stream_id, ptype, payload = wait_for_msg() + if ptype != MSG_READ_MEM_RES: + logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + return payload + +def send_write_mem_req(address, data): + m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data + drv.sendall(m) + +def write_mem(address, data): + send_write_mem_req(address, data) + stream_id, ptype, payload = wait_for_msg() + if ptype != MSG_WRITE_MEM_RES: + logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + +def send_connect_response(stream_id, result): + m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result) + drv.sendall(m) + +def send_data(stream_id, data): + m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data + drv.sendall(m) + +def send_eos(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_EOS) + drv.sendall(m) + +def send_reset(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_RESET) + drv.sendall(m) + +current_stream_id = None +first_msg = True +raw_received = b'' +is_empty = [True, True] + +def process_msg_data(payload): + global ptrs, first_msg, raw_received + + if first_msg: + ptrs = struct.unpack('>II', payload) + logger.debug('Received pointers %s', ptrs) + first_msg = False + return + + buf_index = payload[0] + + if len(raw_received) < 900*2: + if not is_empty[buf_index]: + data = b'\x00' * (900*2) + send_write_mem_req(ptrs[buf_index], data) + is_empty[buf_index] = True + else: + ldata = raw_received[0:900*2:2] + rdata = raw_received[1:900*2:2] + data = ldata + rdata + raw_received = raw_received[900*2:] + send_write_mem_req(ptrs[buf_index], data) + is_empty[buf_index] = False + +def process_drv_msg(stream_id, ptype, payload): + global current_stream_id, first_msg + + if ptype == MSG_CONNECT: + if payload == b'piaudio' and current_stream_id is None: + logger.info('Amiga connected') + current_stream_id = stream_id + first_msg = True + send_connect_response(stream_id, 0) + else: + send_connect_response(stream_id, 3) + elif current_stream_id == stream_id: + if ptype == MSG_DATA: + process_msg_data(payload) + elif ptype == MSG_EOS: + pass + elif ptype == MSG_RESET: + current_stream_id = None + logger.info('Amiga disconnected') + +done = False + +try: + idx = sys.argv.index('-ondemand') +except ValueError: + idx = -1 + +if idx != -1: + fd = int(sys.argv[idx + 1]) + drv = socket.socket(fileno=fd) +else: + drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + drv.connect(('localhost', 7110)) + drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + send_register_req(b'piaudio') + _, _, payload = wait_for_msg() + if payload[0] != 1: + logger.error('Unable to register piaudio with driver, shutting down') + drv.close() + done = True + +rbuf = b'' + +PIPE_NAME = '/tmp/piaudio_pipe' + +if not done: + exists = True + try: + if (os.stat(PIPE_NAME).st_mode & 0o170000) != 0o10000: + logger.error('A file that is not a named pipe exists at ' + PIPE_NAME) + done = True + except: + exists = False + +if not done and not exists: + try: + os.mkfifo(PIPE_NAME) + except: + logger.error('Unable to create named pipe at ' + PIPE_NAME) + done = True + +if not done: + try: + pipe_fd = os.open(PIPE_NAME, os.O_RDONLY | os.O_NONBLOCK) + fcntl.fcntl(pipe_fd, fcntl.F_SETPIPE_SZ, 4096) + except: + logger.error('Unable to open named pipe at ' + PIPE_NAME) + done = True + +if not done: + logger.info('piaudio service is running') + +while not done: + sel_fds = [drv] + + if idx == -1: + sel_fds.append(sys.stdin) + + if len(raw_received) < 900*2: + sel_fds.append(pipe_fd) + + rfd, wfd, xfd = select.select(sel_fds, [], [], 5.0) + + for fd in rfd: + if fd == sys.stdin: + line = sys.stdin.readline() + if not line or line.startswith('quit'): + if current_stream_id is not None: + send_reset(current_stream_id) + drv.close() + done = True + elif fd == drv: + buf = drv.recv(1024) + if not buf: + if current_stream_id is not None: + send_reset(current_stream_id) + drv.close() + done = True + else: + rbuf += buf + while True: + if len(rbuf) < 9: + break + + (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9]) + if len(rbuf) < 9 + plen: + break + + rbuf = rbuf[9:] + payload = rbuf[:plen] + rbuf = rbuf[plen:] + + process_drv_msg(stream_id, ptype, payload) + elif fd == pipe_fd: + data = os.read(pipe_fd, 900*2) + + if len(data) == 0: + os.close(pipe_fd) + + l = len(raw_received) + c = l // (900*2) + if c * 900*2 < l: + raw_received += b'\x00' * ((c + 1) * 900*2 - l) + + pipe_fd = os.open(PIPE_NAME, os.O_RDONLY | os.O_NONBLOCK) + fcntl.fcntl(pipe_fd, fcntl.F_SETPIPE_SZ, 4096) + else: + raw_received += data diff --git a/a314/files_pi/picmd.conf b/a314/files_pi/picmd.conf new file mode 100644 index 0000000..783403a --- /dev/null +++ b/a314/files_pi/picmd.conf @@ -0,0 +1,8 @@ +{ + "paths": ["/home/pi/amiga_sdk/vbcc/bin"], + "env_vars": { + "VBCC": "/home/pi/amiga_sdk/vbcc" + }, + "sgr_map": { + } +} diff --git a/a314/files_pi/picmd.py b/a314/files_pi/picmd.py new file mode 100755 index 0000000..c67ce45 --- /dev/null +++ b/a314/files_pi/picmd.py @@ -0,0 +1,391 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2018-2021 Niklas Ekström + +import select +import sys +import socket +import threading +import time +import os +import struct +import pty +import signal +import termios +import fcntl +import logging +import json + +logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s') +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +FS_CFG_FILE = 'a314/files_pi/a314fs.conf' +PICMD_CFG_FILE = 'a314/files_pi/picmd.conf' + +volume_paths = {} +search_path = '' +env_vars = {} +sgr_map = {} + +def load_cfg(): + with open(FS_CFG_FILE, 'rt') as f: + cfg = json.load(f) + devs = cfg['devices'] + for _, dev in devs.items(): + volume_paths[dev['volume']] = dev['path'] + + global search_path + search_path = os.getenv('PATH') + + with open(PICMD_CFG_FILE, 'rt') as f: + cfg = json.load(f) + + if 'paths' in cfg: + search_path = ':'.join(cfg['paths']) + ':' + search_path + os.environ['PATH'] = search_path + + if 'env_vars' in cfg: + for key, val in cfg['env_vars'].items(): + env_vars[key] = val + + if 'sgr_map' in cfg: + for key, val in cfg['sgr_map'].items(): + sgr_map[key] = str(val) + +load_cfg() + +MSG_REGISTER_REQ = 1 +MSG_REGISTER_RES = 2 +MSG_DEREGISTER_REQ = 3 +MSG_DEREGISTER_RES = 4 +MSG_READ_MEM_REQ = 5 +MSG_READ_MEM_RES = 6 +MSG_WRITE_MEM_REQ = 7 +MSG_WRITE_MEM_RES = 8 +MSG_CONNECT = 9 +MSG_CONNECT_RESPONSE = 10 +MSG_DATA = 11 +MSG_EOS = 12 +MSG_RESET = 13 + +def wait_for_msg(): + header = b'' + while len(header) < 9: + data = drv.recv(9 - len(header)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + header += data + (plen, stream_id, ptype) = struct.unpack('=IIB', header) + payload = b'' + while len(payload) < plen: + data = drv.recv(plen - len(payload)) + if not data: + logger.error('Connection to a314d was closed, terminating.') + exit(-1) + payload += data + return (stream_id, ptype, payload) + +def send_register_req(name): + m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name + drv.sendall(m) + +def send_read_mem_req(address, length): + m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length) + drv.sendall(m) + +def read_mem(address, length): + send_read_mem_req(address, length) + stream_id, ptype, payload = wait_for_msg() + if ptype != MSG_READ_MEM_RES: + logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + return payload + +def send_write_mem_req(address, data): + m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data + drv.sendall(m) + +def write_mem(address, data): + send_write_mem_req(address, data) + stream_id, ptype, payload = wait_for_msg() + if ptype != MSG_WRITE_MEM_RES: + logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype) + exit(-1) + +def send_connect_response(stream_id, result): + m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result) + drv.sendall(m) + +def send_data(stream_id, data): + m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data + drv.sendall(m) + +def send_eos(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_EOS) + drv.sendall(m) + +def send_reset(stream_id): + m = struct.pack('=IIB', 0, stream_id, MSG_RESET) + drv.sendall(m) + +sessions = {} + +class PiCmdSession(object): + def __init__(self, stream_id): + self.stream_id = stream_id + self.pid = 0 + + self.first_packet = True + self.reset_after = None + + self.rasp_was_esc = False + self.rasp_in_cs = False + self.rasp_holding = '' + + self.amiga_in_cs = False + self.amiga_holding = '' + + def process_amiga_ansi(self, data): + data = data.decode('latin-1') + out = '' + for c in data: + if not self.amiga_in_cs: + if c == '\x9b': + self.amiga_in_cs = True + self.amiga_holding = '\x1b[' + else: + out += c + else: # self.amiga_in_cs + self.amiga_holding += c + if c >= chr(0x40) and c <= chr(0x7e): + if c == 'r': + # Window Bounds Report + # ESC[1;1;rows;cols r + rows, cols = map(int, self.amiga_holding[6:-2].split(';')) + winsize = struct.pack('HHHH', rows, cols, 0, 0) + fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize) + elif c == '|': + # Input Event Report + # ESC[12;0;0;x;x;x;x;x| + # Window resized + send_data(self.stream_id, b'\x9b' + b'0 q') + else: + out += self.amiga_holding + self.amiga_holding = '' + self.amiga_in_cs = False + if len(out) != 0: + os.write(self.fd, out.encode('utf-8')) + + def process_msg_data(self, data): + if self.first_packet: + if len(data) != 8: + send_reset(self.stream_id) + del sessions[self.stream_id] + else: + address, length = struct.unpack('>II', data) + buf = read_mem(address, length) + + ind = 0 + rows, cols = struct.unpack('>HH', buf[ind:ind+4]) + ind += 4 + + component_count = buf[ind] + ind += 1 + + components = [] + for _ in range(component_count): + n = buf[ind] + ind += 1 + components.append(buf[ind:ind+n].decode('latin-1')) + ind += n + + arg_count = buf[ind] + ind += 1 + + args = [] + for _ in range(arg_count): + n = buf[ind] + ind += 1 + args.append(buf[ind:ind+n].decode('latin-1')) + ind += n + + if arg_count == 0: + args.append('bash') + + self.pid, self.fd = pty.fork() + if self.pid == 0: + for key, val in env_vars.items(): + os.putenv(key, val) + os.putenv('PATH', search_path) + os.putenv('TERM', 'ansi') + winsize = struct.pack('HHHH', rows, cols, 0, 0) + fcntl.ioctl(sys.stdin, termios.TIOCSWINSZ, winsize) + if component_count != 0 and components[0] in volume_paths: + path = volume_paths[components[0]] + os.chdir(os.path.join(path, *components[1:])) + else: + os.chdir(os.getenv('HOME', '/')) + os.execvp(args[0], args) + + self.first_packet = False + + elif self.pid: + self.process_amiga_ansi(data) + + def close(self): + if self.pid: + os.kill(self.pid, signal.SIGTERM) + self.pid = 0 + os.close(self.fd) + del sessions[self.stream_id] + + def process_rasp_ansi(self, text): + text = text.decode('utf-8') + out = '' + for c in text: + if not self.rasp_in_cs: + if not self.rasp_was_esc: + if c == '\x1b': + self.rasp_was_esc = True + else: + out += c + else: # self.rasp_was_esc + if c == '[': + self.rasp_was_esc = False + self.rasp_in_cs = True + self.rasp_holding = '\x1b[' + elif c == '\x1b': + out += '\x1b' + else: + out += '\x1b' + out += c + self.rasp_was_esc = False + else: # self.rasp_in_cs + self.rasp_holding += c + if c >= chr(0x40) and c <= chr(0x7e): + if c == 'm': + # Select Graphic Rendition + # ESC[30;37m + attrs = self.rasp_holding[2:-1].split(';') + attrs = [sgr_map[a] if a in sgr_map else a for a in attrs] + out += '\x1b[' + (';'.join(attrs)) + 'm' + else: + out += self.rasp_holding + self.rasp_holding = '' + self.rasp_in_cs = False + return out.encode('latin-1', 'replace') + + def handle_text(self): + try: + text = os.read(self.fd, 1024) + text = self.process_rasp_ansi(text) + while len(text) > 0: + take = min(len(text), 252) + send_data(self.stream_id, text[:take]) + text = text[take:] + except: + #os.close(self.fd) + os.kill(self.pid, signal.SIGTERM) + self.pid = 0 + send_eos(self.stream_id) + self.reset_after = time.time() + 10 + + def handle_timeout(self): + if self.reset_after and self.reset_after < time.time(): + send_reset(self.stream_id) + del sessions[self.stream_id] + + def fileno(self): + return self.fd + +def process_drv_msg(stream_id, ptype, payload): + if ptype == MSG_CONNECT: + if payload == b'picmd': + s = PiCmdSession(stream_id) + sessions[stream_id] = s + send_connect_response(stream_id, 0) + else: + send_connect_response(stream_id, 3) + elif stream_id in sessions: + s = sessions[stream_id] + + if ptype == MSG_DATA: + s.process_msg_data(payload) + elif ptype == MSG_EOS: + if s.pid: + send_eos(s.stream_id) + s.close() + elif ptype == MSG_RESET: + s.close() + +done = False + +try: + idx = sys.argv.index('-ondemand') +except ValueError: + idx = -1 + +if idx != -1: + fd = int(sys.argv[idx + 1]) + drv = socket.socket(fileno=fd) +else: + drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + drv.connect(('localhost', 7110)) + drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + send_register_req(b'picmd') + _, _, payload = wait_for_msg() + if payload[0] != 1: + logger.error('Unable to register picmd with driver, shutting down') + drv.close() + done = True + +rbuf = b'' + +if not done: + logger.info('picmd server is running') + +while not done: + sel_fds = [drv] + [s for s in sessions.values() if s.pid] + if idx == -1: + sel_fds.append(sys.stdin) + rfd, wfd, xfd = select.select(sel_fds, [], [], 5.0) + + for fd in rfd: + if fd == sys.stdin: + line = sys.stdin.readline() + if not line or line.startswith('quit'): + for s in sessions.values(): + s.close() + drv.close() + done = True + elif fd == drv: + buf = drv.recv(1024) + if not buf: + for s in sessions.values(): + s.close() + drv.close() + done = True + else: + rbuf += buf + while True: + if len(rbuf) < 9: + break + + (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9]) + if len(rbuf) < 9 + plen: + break + + rbuf = rbuf[9:] + payload = rbuf[:plen] + rbuf = rbuf[plen:] + + process_drv_msg(stream_id, ptype, payload) + else: + fd.handle_text() + + for s in sessions.values(): + s.handle_timeout() diff --git a/a314/software-amiga/a314.device b/a314/software-amiga/a314.device new file mode 100644 index 0000000..9efa80d Binary files /dev/null and b/a314/software-amiga/a314.device differ diff --git a/a314/software-amiga/a314eth.device b/a314/software-amiga/a314eth.device new file mode 100644 index 0000000..2801dfb Binary files /dev/null and b/a314/software-amiga/a314eth.device differ diff --git a/a314/software-amiga/a314fs b/a314/software-amiga/a314fs new file mode 100644 index 0000000..4bec5d9 Binary files /dev/null and b/a314/software-amiga/a314fs differ diff --git a/a314/software-amiga/a314fs-mountlist b/a314/software-amiga/a314fs-mountlist new file mode 100644 index 0000000..4f8e20c --- /dev/null +++ b/a314/software-amiga/a314fs-mountlist @@ -0,0 +1,12 @@ +PI0: FileSystem = l:a314fs + Device = a314.device + Unit = 0 + Surfaces = 1 + BlocksPerTrack = 10 + Reserved = 2 + LowCyl = 0 + HighCyl = 100 + StackSize = 1000 + Mount = 1 + DosType = 0x33313400 +# diff --git a/a314/software-amiga/a314fs_pistorm/a314fs.c b/a314/software-amiga/a314fs_pistorm/a314fs.c new file mode 100644 index 0000000..b07e801 --- /dev/null +++ b/a314/software-amiga/a314fs_pistorm/a314fs.c @@ -0,0 +1,1305 @@ +/* + * Copyright (c) 2018-2021 Niklas Ekström + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include "../../a314device/a314.h" +#include "../../a314device/proto_a314.h" + +#include "messages.h" + +#define MKBADDRU(x) (((ULONG)x) >> 2) + +#define ID_314_DISK (('3' << 24) | ('1' << 16) | ('4' << 8)) + +#define REQ_RES_BUF_SIZE 256 +#define BUFFER_SIZE 4096 + +// Not defined if using NDK13 +#ifndef ACTION_EXAMINE_FH +#define ACTION_EXAMINE_FH 1034 +#endif + +#ifndef ACTION_SAME_LOCK +#define ACTION_SAME_LOCK 40 +#endif + +// Grab a reserved action type which seems to be unused +#define ACTION_UNSUPPORTED 65535 + +struct ExecBase *SysBase; +struct DosLibrary *DOSBase; +struct MsgPort *mp; + +struct DeviceList *my_volume; +char default_volume_name[] = "\006PiDisk"; + +char device_name[32]; // "\004PI0:" + +struct MsgPort *timer_mp; +struct timerequest *tr; + +struct MsgPort *a314_mp; +struct A314_IORequest *a314_ior; + +struct Library *A314Base; + +long socket; + +// These are allocated in A314 memory. +char *request_buffer = NULL; +char *data_buffer = NULL; + +void MyNewList(struct List *l) +{ + l->lh_Head = (struct Node *)&(l->lh_Tail); + l->lh_Tail = NULL; + l->lh_TailPred = (struct Node *)&(l->lh_Head); +} + +struct MsgPort *MyCreatePort(char *name, long pri) +{ + int sigbit = AllocSignal(-1); + if (sigbit == -1) + return NULL; + + struct MsgPort *port = (struct MsgPort *)AllocMem(sizeof(struct MsgPort), MEMF_PUBLIC | MEMF_CLEAR); + if (!port) + { + FreeSignal(sigbit); + return NULL; + } + + port->mp_Node.ln_Name = name; + port->mp_Node.ln_Pri = pri; + port->mp_Node.ln_Type = NT_MSGPORT; + port->mp_Flags = PA_SIGNAL; + port->mp_SigBit = sigbit; + port->mp_SigTask = FindTask(NULL); + + if (name) + AddPort(port); + else + MyNewList(&(port->mp_MsgList)); + + return port; +} + +struct IORequest *MyCreateExtIO(struct MsgPort *ioReplyPort, ULONG size) +{ + if (!ioReplyPort) + return NULL; + + struct IORequest *ioReq = (struct IORequest *)AllocMem(size, MEMF_PUBLIC | MEMF_CLEAR); + if (!ioReq) + return NULL; + + ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE; + ioReq->io_Message.mn_Length = size; + ioReq->io_Message.mn_ReplyPort = ioReplyPort; + + return ioReq; +} + +#define DEBUG 0 + +#if !DEBUG +void dbg_init() +{ +} + +void dbg(const char* fmt, ...) +{ +} +#else +struct FileHandle *dbg_log; +struct MsgPort *dbg_replyport; +struct StandardPacket *dbg_sp; +char dbg_buf[256]; + +void dbg_init() +{ + dbg_log = (struct FileHandle *)BADDR(Open("CON:200/0/440/256/a314fs", MODE_NEWFILE)); + dbg_replyport = MyCreatePort(NULL, 0); + dbg_sp = (struct StandardPacket *)AllocMem(sizeof(struct StandardPacket), MEMF_PUBLIC | MEMF_CLEAR); +} + +void dbg_out(int l) +{ + dbg_sp->sp_Msg.mn_Node.ln_Name = (char *)&(dbg_sp->sp_Pkt); + dbg_sp->sp_Pkt.dp_Link = &(dbg_sp->sp_Msg); + dbg_sp->sp_Pkt.dp_Port = dbg_replyport; + dbg_sp->sp_Pkt.dp_Type = ACTION_WRITE; + dbg_sp->sp_Pkt.dp_Arg1 = (long)dbg_log->fh_Arg1; + dbg_sp->sp_Pkt.dp_Arg2 = (long)dbg_buf; + dbg_sp->sp_Pkt.dp_Arg3 = (long)l - 1; + + PutMsg((struct MsgPort *)dbg_log->fh_Type, (struct Message *)dbg_sp); + WaitPort(dbg_replyport); + GetMsg(dbg_replyport); +} + +void dbg(const char* fmt, ...) +{ + char numbuf[16]; + + const char *p = fmt; + char *q = dbg_buf; + + va_list args; + va_start(args, fmt); + while (*p != 0) + { + char c = *p++; + if (c == '$') + { + c = *p++; + if (c == 'b') + { + UBYTE x = va_arg(args, UBYTE); + *q++ = '$'; + for (int i = 0; i < 2; i++) + { + int ni = (x >> ((1 - i) * 4)) & 0xf; + *q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni); + } + } + else if (c == 'w') + { + UWORD x = va_arg(args, UWORD); + *q++ = '$'; + for (int i = 0; i < 4; i++) + { + int ni = (x >> ((3 - i) * 4)) & 0xf; + *q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni); + } + } + else if (c == 'l') + { + ULONG x = va_arg(args, ULONG); + *q++ = '$'; + for (int i = 0; i < 8; i++) + { + int ni = (x >> ((7 - i) * 4)) & 0xf; + *q++ = (ni >= 10) ? ('a' + (ni - 10)) : ('0' + ni); + } + } + else if (c == 'S') + { + unsigned char *s = (unsigned char *)va_arg(args, ULONG); + int l = *s++; + for (int i = 0; i < l; i++) + *q++ = *s++; + } + else if (c == 's') + { + unsigned char *s = (unsigned char *)va_arg(args, ULONG); + while (*s) + *q++ = *s++; + *q++ = 0; + } + } + else + { + *q++ = c; + } + } + *q++ = 0; + + va_end(args); + + dbg_out(q - dbg_buf); +} +#endif + +char *DosAllocMem(int len) +{ + long *p = (long *)AllocMem(len + 4, MEMF_PUBLIC | MEMF_CLEAR); + *p++ = len; + return((char *)p); +} + +void DosFreeMem(char *p) +{ + long *lp = (long *)p; + long len = *--lp; + FreeMem((char *)lp, len); +} + +void reply_packet(struct DosPacket *dp) +{ + struct MsgPort *reply_port = dp->dp_Port; + dp->dp_Port = mp; + PutMsg(reply_port, dp->dp_Link); +} + +// Routines for talking to the A314 driver. +LONG a314_cmd_wait(UWORD command, char *buffer, int length) +{ + a314_ior->a314_Request.io_Command = command; + a314_ior->a314_Request.io_Error = 0; + a314_ior->a314_Socket = socket; + a314_ior->a314_Buffer = buffer; + a314_ior->a314_Length = length; + return DoIO((struct IORequest *)a314_ior); +} + +LONG a314_connect(char *name) +{ + struct DateStamp ds; + DateStamp(&ds); + socket = ds.ds_Tick; + return a314_cmd_wait(A314_CONNECT, name, strlen(name)); +} + +LONG a314_read(char *buf, int length) +{ + return a314_cmd_wait(A314_READ, buf, length); +} + +LONG a314_write(char *buf, int length) +{ + return a314_cmd_wait(A314_WRITE, buf, length); +} + +LONG a314_eos() +{ + return a314_cmd_wait(A314_EOS, NULL, 0); +} + +LONG a314_reset() +{ + return a314_cmd_wait(A314_RESET, NULL, 0); +} + +void create_and_add_volume() +{ + my_volume = (struct DeviceList *)DosAllocMem(sizeof(struct DeviceList)); + my_volume->dl_Name = MKBADDRU(default_volume_name); + my_volume->dl_Type = DLT_VOLUME; + my_volume->dl_Task = mp; + my_volume->dl_DiskType = ID_314_DISK; + DateStamp(&my_volume->dl_VolumeDate); + + struct RootNode *root = (struct RootNode *)DOSBase->dl_Root; + struct DosInfo *info = (struct DosInfo *)BADDR(root->rn_Info); + + Forbid(); + my_volume->dl_Next = info->di_DevInfo; + info->di_DevInfo = MKBADDRU(my_volume); + Permit(); +} + +void startup_fs_handler(struct DosPacket *dp) +{ + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg1); + struct FileSysStartupMsg *fssm = (struct FileSysStartupMsg *)BADDR(dp->dp_Arg2); + struct DeviceNode *node = (struct DeviceNode *)BADDR(dp->dp_Arg3); + + memcpy(device_name, name, *name + 1); + device_name[*name + 1] = 0; + + node->dn_Task = mp; + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + reply_packet(dp); + + dbg_init(); + + dbg("ACTION_STARTUP\n"); + dbg(" device_name = $S\n", (ULONG)device_name); + dbg(" fssm = $l\n", (ULONG)fssm); + dbg(" node = $l\n", (ULONG)node); + + timer_mp = MyCreatePort(NULL, 0); + tr = (struct timerequest *)MyCreateExtIO(timer_mp, sizeof(struct timerequest)); + if (OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)tr, 0) != 0) + { + // If this happens, there's nothing we can do. + // For now, assume this does not happen. + dbg("Fatal error: unable to open timer.device\n"); + return; + } + + a314_mp = MyCreatePort(NULL, 0); + a314_ior = (struct A314_IORequest *)MyCreateExtIO(a314_mp, sizeof(struct A314_IORequest)); + if (OpenDevice(A314_NAME, 0, (struct IORequest *)a314_ior, 0) != 0) + { + // If this fails, there's nothing we can do. + // For now, assume this does not happen. + dbg("Fatal error: unable to open a314.device\n"); + return; + } + + A314Base = &(a314_ior->a314_Request.io_Device->dd_Library); + + if (a314_connect("a314fs") != A314_CONNECT_OK) + { + dbg("Fatal error: unable to connect to a314fs on rasp\n"); + // This COULD happen. + // If it DOES happen, we just wait for a bit and try again. + + // TODO: Have to use timer.device to set a timer for ~2 seconds and then try connecting again. + return; + } + + request_buffer = AllocMem(REQ_RES_BUF_SIZE, MEMF_FAST); + data_buffer = AllocMem(BUFFER_SIZE, MEMF_FAST); + + // We can assume that we arrive here, and have a stream to the Pi side, to where we can transfer data. + create_and_add_volume(); + + dbg("Startup successful\n"); + + // If we end up having problems with the connections, treat the disc as ejected, and try inserting it again in two seconds. + // TODO: This is not currently handled. +} + +void wait_for_response() +{ + for (unsigned long i = 0; 1; i++) + { + if (*request_buffer) + { + dbg("--Got response after $l sleeps\n", i); + return; + } + + tr->tr_node.io_Command = TR_ADDREQUEST; + tr->tr_node.io_Message.mn_ReplyPort = timer_mp; + tr->tr_time.tv_secs = 0; + tr->tr_time.tv_micro = 1000; + DoIO((struct IORequest *)tr); + } +} + +void write_req_and_wait_for_res(int len) +{ + ULONG buf[2] = {TranslateAddressA314(request_buffer), len}; + a314_write((char *)&buf[0], 8); + //wait_for_response(); + a314_read((char *)&buf[0], 8); +} + +struct FileLock *create_and_add_file_lock(long key, long mode) +{ + struct FileLock *lock = (struct FileLock *)DosAllocMem(sizeof(struct FileLock)); + + lock->fl_Key = key; + lock->fl_Access = mode; + lock->fl_Task = mp; + lock->fl_Volume = MKBADDRU(my_volume); + + Forbid(); + lock->fl_Link = my_volume->dl_Lock; + my_volume->dl_Lock = MKBADDRU(lock); + Permit(); + + return lock; +} + +void action_locate_object(struct DosPacket *dp) +{ + struct FileLock *parent = (struct FileLock *)BADDR(dp->dp_Arg1); + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2); + long mode = dp->dp_Arg3; + + dbg("ACTION_LOCATE_OBJECT\n"); + dbg(" parent lock = $l\n", parent); + dbg(" name = $S\n", name); + dbg(" mode = $s\n", mode == SHARED_LOCK ? "SHARED_LOCK" : "EXCLUSIVE_LOCK"); + + struct LocateObjectRequest *req = (struct LocateObjectRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = parent == NULL ? 0 : parent->fl_Key; + req->mode = mode; + + int nlen = *name; + memcpy(req->name, name, nlen + 1); + + write_req_and_wait_for_res(sizeof(struct LocateObjectRequest) + nlen); + + struct LocateObjectResponse *res = (struct LocateObjectResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + struct FileLock *lock = create_and_add_file_lock(res->key, mode); + + dbg(" Returning lock $l\n", lock); + dp->dp_Res1 = MKBADDRU(lock); + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_free_lock(struct DosPacket *dp) +{ + ULONG arg1 = dp->dp_Arg1; + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1); + + dbg("ACTION_FREE_LOCK\n"); + dbg(" lock = $l\n", lock); + + if (lock != NULL) + { + struct FreeLockRequest *req = (struct FreeLockRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock->fl_Key; + + write_req_and_wait_for_res(sizeof(struct FreeLockRequest)); + + // Ignore the response. Must succeed. + //struct FreeLockResponse *res = (struct FreeLockResponse *)request_buffer; + + Forbid(); + if (my_volume->dl_Lock == arg1) + my_volume->dl_Lock = lock->fl_Link; + else + { + struct FileLock *prev = (struct FileLock *)BADDR(my_volume->dl_Lock); + while (prev->fl_Link != arg1) + prev = (struct FileLock *)BADDR(prev->fl_Link); + prev->fl_Link = lock->fl_Link; + } + Permit(); + DosFreeMem((char *)lock); + } + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + reply_packet(dp); +} + +void action_copy_dir(struct DosPacket *dp) +{ + struct FileLock *parent = (struct FileLock *)BADDR(dp->dp_Arg1); + + dbg("ACTION_COPY_DIR\n"); + dbg(" lock to duplicate = $l\n", parent); + + if (parent == NULL) + { + dp->dp_Res1 = 0; + dp->dp_Res2 = 0; + } + else + { + struct CopyDirRequest *req = (struct CopyDirRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = parent->fl_Key; + + write_req_and_wait_for_res(sizeof(struct CopyDirRequest)); + + struct CopyDirResponse *res = (struct CopyDirResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + struct FileLock *lock = create_and_add_file_lock(res->key, parent->fl_Access); + + dbg(" Returning lock $l\n", lock); + dp->dp_Res1 = MKBADDRU(lock); + dp->dp_Res2 = 0; + } + } + + reply_packet(dp); +} + +void action_parent(struct DosPacket *dp) +{ + struct FileLock *prev_lock = (struct FileLock *)BADDR(dp->dp_Arg1); + + dbg("ACTION_PARENT\n"); + dbg(" lock = $l\n", prev_lock); + + if (prev_lock == NULL) + { + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = ERROR_INVALID_LOCK; + } + else + { + struct ParentRequest *req = (struct ParentRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = prev_lock->fl_Key; + + write_req_and_wait_for_res(sizeof(struct ParentRequest)); + + struct ParentResponse *res = (struct ParentResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else if (res->key == 0) + { + dp->dp_Res1 = 0; + dp->dp_Res2 = 0; + } + else + { + struct FileLock *lock = create_and_add_file_lock(res->key, SHARED_LOCK); + + dbg(" Returning lock $l\n", lock); + dp->dp_Res1 = MKBADDRU(lock); + dp->dp_Res2 = 0; + } + } + + reply_packet(dp); +} + +void action_examine_object(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1); + struct FileInfoBlock *fib = (struct FileInfoBlock *)BADDR(dp->dp_Arg2); + + dbg("ACTION_EXAMINE_OBJECT\n"); + dbg(" lock = $l\n", lock); + dbg(" fib = $l\n", fib); + + struct ExamineObjectRequest *req = (struct ExamineObjectRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + + write_req_and_wait_for_res(sizeof(struct ExamineObjectRequest)); + + struct ExamineObjectResponse *res = (struct ExamineObjectResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + int nlen = (unsigned char)(res->data[0]); + memcpy(fib->fib_FileName, res->data, nlen + 1); + fib->fib_FileName[nlen + 1] = 0; + + int clen = (unsigned char)(res->data[nlen + 1]); + memcpy(fib->fib_Comment, &(res->data[nlen + 1]), clen + 1); + fib->fib_Comment[clen + 1] = 0; + + fib->fib_DiskKey = res->disk_key; + fib->fib_DirEntryType = res->entry_type; + fib->fib_EntryType = res->entry_type; + fib->fib_Protection = res->protection; + fib->fib_Size = res->size; + fib->fib_NumBlocks = (res->size + 511) >> 9; + fib->fib_Date.ds_Days = res->date[0]; + fib->fib_Date.ds_Minute = res->date[1]; + fib->fib_Date.ds_Tick = res->date[2]; + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_examine_next(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1); + struct FileInfoBlock *fib = (struct FileInfoBlock *)BADDR(dp->dp_Arg2); + + dbg("ACTION_EXAMINE_NEXT\n"); + dbg(" lock = $l\n", lock); + dbg(" fib = $l\n", fib); + + struct ExamineNextRequest *req = (struct ExamineNextRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + req->disk_key = fib->fib_DiskKey; + + write_req_and_wait_for_res(sizeof(struct ExamineNextRequest)); + + struct ExamineNextResponse *res = (struct ExamineNextResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + int nlen = (unsigned char)(res->data[0]); + memcpy(fib->fib_FileName, res->data, nlen + 1); + fib->fib_FileName[nlen + 1] = 0; + + int clen = (unsigned char)(res->data[nlen + 1]); + memcpy(fib->fib_Comment, &(res->data[nlen + 1]), clen + 1); + fib->fib_Comment[clen + 1] = 0; + + fib->fib_DiskKey = res->disk_key; + fib->fib_DirEntryType = res->entry_type; + fib->fib_EntryType = res->entry_type; + fib->fib_Protection = res->protection; + fib->fib_Size = res->size; + fib->fib_NumBlocks = (res->size + 511) >> 9; + fib->fib_Date.ds_Days = res->date[0]; + fib->fib_Date.ds_Minute = res->date[1]; + fib->fib_Date.ds_Tick = res->date[2]; + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_examine_fh(struct DosPacket *dp) +{ + ULONG arg1 = dp->dp_Arg1; + struct FileInfoBlock *fib = (struct FileInfoBlock *)BADDR(dp->dp_Arg2); + + dbg("ACTION_EXAMINE_FH\n"); + dbg(" arg1 = $l\n", arg1); + dbg(" fib = $l\n", fib); + + struct ExamineFhRequest *req = (struct ExamineFhRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->arg1 = arg1; + + write_req_and_wait_for_res(sizeof(struct ExamineFhRequest)); + + struct ExamineFhResponse *res = (struct ExamineFhResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + int nlen = (unsigned char)(res->data[0]); + memcpy(fib->fib_FileName, res->data, nlen + 1); + fib->fib_FileName[nlen + 1] = 0; + + int clen = (unsigned char)(res->data[nlen + 1]); + memcpy(fib->fib_Comment, &(res->data[nlen + 1]), clen + 1); + fib->fib_Comment[clen + 1] = 0; + + fib->fib_DiskKey = res->disk_key; + fib->fib_DirEntryType = res->entry_type; + fib->fib_EntryType = res->entry_type; + fib->fib_Protection = res->protection; + fib->fib_Size = res->size; + fib->fib_NumBlocks = (res->size + 511) >> 9; + fib->fib_Date.ds_Days = res->date[0]; + fib->fib_Date.ds_Minute = res->date[1]; + fib->fib_Date.ds_Tick = res->date[2]; + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_findxxx(struct DosPacket *dp) +{ + struct FileHandle *fh = (struct FileHandle *)BADDR(dp->dp_Arg1); + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg2); + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg3); + + if (dp->dp_Type == ACTION_FINDUPDATE) + dbg("ACTION_FINDUPDATE\n"); + else if (dp->dp_Type == ACTION_FINDINPUT) + dbg("ACTION_FINDINPUT\n"); + else if (dp->dp_Type == ACTION_FINDOUTPUT) + dbg("ACTION_FINDOUTPUT\n"); + + dbg(" file handle = $l\n", fh); + dbg(" lock = $l\n", lock); + dbg(" name = $S\n", name); + + struct FindXxxRequest *req = (struct FindXxxRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + + int nlen = *name; + memcpy(req->name, name, nlen + 1); + + write_req_and_wait_for_res(sizeof(struct FindXxxRequest) + nlen); + + struct FindXxxResponse *res = (struct FindXxxResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + fh->fh_Arg1 = res->arg1; + fh->fh_Type = mp; + fh->fh_Port = DOSFALSE; // Not an interactive file. + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_read(struct DosPacket *dp) +{ + ULONG arg1 = dp->dp_Arg1; + UBYTE *dst = (UBYTE *)dp->dp_Arg2; + int length = dp->dp_Arg3; + + dbg("ACTION_READ\n"); + dbg(" arg1 = $l\n", arg1); + dbg(" length = $l\n", length); + + if (length == 0) + { + dp->dp_Res1 = -1; + dp->dp_Res2 = ERROR_INVALID_LOCK; // This is not the correct error. + reply_packet(dp); + return; + } + + int total_read = 0; + while (length) + { + int to_read = length; + if (to_read > BUFFER_SIZE) + to_read = BUFFER_SIZE; + + struct ReadRequest *req = (struct ReadRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->arg1 = arg1; + req->address = TranslateAddressA314(data_buffer); + req->length = to_read; + + write_req_and_wait_for_res(sizeof(struct ReadRequest)); + + struct ReadResponse *res = (struct ReadResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = -1; + dp->dp_Res2 = res->error_code; + reply_packet(dp); + return; + } + + if (res->actual) + { + memcpy(dst, data_buffer, res->actual); + dst += res->actual; + total_read += res->actual; + length -= res->actual; + } + + if (res->actual < to_read) + break; + } + + dp->dp_Res1 = total_read; + dp->dp_Res2 = 0; + reply_packet(dp); +} + +void action_write(struct DosPacket *dp) +{ + ULONG arg1 = dp->dp_Arg1; + UBYTE *src = (UBYTE *)dp->dp_Arg2; + int length = dp->dp_Arg3; + + dbg("ACTION_WRITE\n"); + dbg(" arg1 = $l\n", arg1); + dbg(" length = $l\n", length); + + int total_written = 0; + while (length) + { + int to_write = length; + if (to_write > BUFFER_SIZE) + to_write = BUFFER_SIZE; + + memcpy(data_buffer, src, to_write); + + struct WriteRequest *req = (struct WriteRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->arg1 = arg1; + req->address = TranslateAddressA314(data_buffer); + req->length = to_write; + + write_req_and_wait_for_res(sizeof(struct WriteRequest)); + + struct WriteResponse *res = (struct WriteResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = total_written; + dp->dp_Res2 = res->error_code; + reply_packet(dp); + return; + } + + if (res->actual) + { + src += res->actual; + total_written += res->actual; + length -= res->actual; + } + + if (res->actual < to_write) + break; + } + + dp->dp_Res1 = total_written; + dp->dp_Res2 = 0; + reply_packet(dp); +} + +void action_seek(struct DosPacket *dp) +{ + ULONG arg1 = dp->dp_Arg1; + LONG new_pos = dp->dp_Arg2; + LONG mode = dp->dp_Arg3; + + dbg("ACTION_SEEK\n"); + dbg(" arg1 = $l\n", arg1); + dbg(" new_pos = $l\n", new_pos); + dbg(" mode = $l\n", mode); + + struct SeekRequest *req = (struct SeekRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->arg1 = arg1; + req->new_pos = new_pos; + req->mode = mode; + + write_req_and_wait_for_res(sizeof(struct SeekRequest)); + + struct SeekResponse *res = (struct SeekResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = -1; + dp->dp_Res2 = res->error_code; + } + else + { + dp->dp_Res1 = res->old_pos; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_end(struct DosPacket *dp) +{ + ULONG arg1 = dp->dp_Arg1; + + dbg("ACTION_END\n"); + dbg(" arg1 = $l\n", arg1); + + struct EndRequest *req = (struct EndRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->arg1 = arg1; + + write_req_and_wait_for_res(sizeof(struct EndRequest)); + + //struct EndResponse *res = (struct EndResponse *)request_buffer; + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + reply_packet(dp); +} + +void action_delete_object(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1); + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2); + + dbg("ACTION_DELETE_OBJECT\n"); + dbg(" lock = $l\n", lock); + dbg(" name = $S\n", name); + + struct DeleteObjectRequest *req = (struct DeleteObjectRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + + int nlen = *name; + memcpy(req->name, name, nlen + 1); + + write_req_and_wait_for_res(sizeof(struct DeleteObjectRequest) + nlen); + + struct DeleteObjectResponse *res = (struct DeleteObjectResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_rename_object(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1); + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2); + struct FileLock *target_dir = (struct FileLock *)BADDR(dp->dp_Arg3); + unsigned char *new_name = (unsigned char *)BADDR(dp->dp_Arg4); + + dbg("ACTION_RENAME_OBJECT\n"); + dbg(" lock = $l\n", lock); + dbg(" name = $S\n", name); + dbg(" target directory = $l\n", lock); + dbg(" new name = $S\n", new_name); + + struct RenameObjectRequest *req = (struct RenameObjectRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + req->target_dir = target_dir == NULL ? 0 : target_dir->fl_Key; + + int nlen = *name; + int nnlen = *new_name; + + req->name_len = nlen; + req->new_name_len = nnlen; + + unsigned char *p = &(req->new_name_len) + 1; + memcpy(p, name + 1, nlen); + p += nlen; + memcpy(p, new_name + 1, nnlen); + + write_req_and_wait_for_res(sizeof(struct RenameObjectRequest) + nlen + nnlen); + + struct RenameObjectResponse *res = (struct RenameObjectResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_create_dir(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1); + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg2); + + dbg("ACTION_CREATE_DIR\n"); + dbg(" lock = $l\n", lock); + dbg(" name = $S\n", name); + + struct CreateDirRequest *req = (struct CreateDirRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + + int nlen = *name; + memcpy(req->name, name, nlen + 1); + + write_req_and_wait_for_res(sizeof(struct CreateDirRequest) + nlen); + + struct CreateDirResponse *res = (struct CreateDirResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + struct FileLock *lock = create_and_add_file_lock(res->key, SHARED_LOCK); + + dbg(" Returning lock $l\n", lock); + dp->dp_Res1 = MKBADDRU(lock); + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_set_protect(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg2); + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg3); + long mask = dp->dp_Arg4; + + dbg("ACTION_SET_PROTECT\n"); + dbg(" lock = $l\n", lock); + dbg(" name = $S\n", name); + dbg(" mask = $l\n", mask); + + struct SetProtectRequest *req = (struct SetProtectRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + req->mask = mask; + + int nlen = *name; + memcpy(req->name, name, nlen + 1); + + write_req_and_wait_for_res(sizeof(struct SetProtectRequest) + nlen); + + struct SetProtectResponse *res = (struct SetProtectResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_set_comment(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg2); + unsigned char *name = (unsigned char *)BADDR(dp->dp_Arg3); + unsigned char *comment = (unsigned char *)BADDR(dp->dp_Arg4); + + dbg("ACTION_SET_COMMENT\n"); + dbg(" lock = $l\n", lock); + dbg(" name = $S\n", name); + dbg(" comment = $S\n", comment); + + struct SetCommentRequest *req = (struct SetCommentRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key = lock == NULL ? 0 : lock->fl_Key; + + int nlen = *name; + int clen = *comment; + + req->name_len = nlen; + req->comment_len = clen; + + unsigned char *p = &(req->comment_len) + 1; + memcpy(p, name + 1, nlen); + p += nlen; + memcpy(p, comment + 1, clen); + + write_req_and_wait_for_res(sizeof(struct SetCommentRequest) + nlen + clen); + + struct SetCommentResponse *res = (struct SetCommentResponse *)request_buffer; + if (!res->success) + { + dbg(" Failed, error code $l\n", (LONG)res->error_code); + dp->dp_Res1 = DOSFALSE; + dp->dp_Res2 = res->error_code; + } + else + { + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + } + + reply_packet(dp); +} + +void action_same_lock(struct DosPacket *dp) +{ + struct FileLock *lock1 = (struct FileLock *)BADDR(dp->dp_Arg1); + struct FileLock *lock2 = (struct FileLock *)BADDR(dp->dp_Arg2); + + dbg("ACTION_SAME_LOCK\n"); + dbg(" locks to compare = $l $l\n", lock1, lock2); + + struct SameLockRequest *req = (struct SameLockRequest *)request_buffer; + req->has_response = 0; + req->type = dp->dp_Type; + req->key1 = lock1->fl_Key; + req->key2 = lock2->fl_Key; + + write_req_and_wait_for_res(sizeof(struct SameLockRequest)); + + struct SameLockResponse *res = (struct SameLockResponse *)request_buffer; + dp->dp_Res1 = res->success ? DOSTRUE : DOSFALSE; + dp->dp_Res2 = res->error_code; + + reply_packet(dp); +} + +void fill_info_data(struct InfoData *id) +{ + memset(id, 0, sizeof(struct InfoData)); + id->id_DiskState = ID_VALIDATED; + id->id_NumBlocks = 512 * 1024; + id->id_NumBlocksUsed = 10; + id->id_BytesPerBlock = 512; + id->id_DiskType = my_volume->dl_DiskType; + id->id_VolumeNode = MKBADDRU(my_volume); + id->id_InUse = DOSTRUE; +} + +void action_info(struct DosPacket *dp) +{ + struct FileLock *lock = (struct FileLock *)BADDR(dp->dp_Arg1); + struct InfoData *id = (struct InfoData *)BADDR(dp->dp_Arg2); + + dbg("ACTION_INFO\n"); + dbg(" lock = $l\n", lock); + + fill_info_data(id); + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + reply_packet(dp); +} + +void action_disk_info(struct DosPacket *dp) +{ + struct InfoData *id = (struct InfoData *)BADDR(dp->dp_Arg1); + + dbg("ACTION_DISK_INFO\n"); + + fill_info_data(id); + + dp->dp_Res1 = DOSTRUE; + dp->dp_Res2 = 0; + reply_packet(dp); +} + +void action_unsupported(struct DosPacket *dp) +{ + dbg("ACTION_UNSUPPORTED\n"); + dbg(" Unsupported action: $l\n", (ULONG)dp->dp_Type); + + struct UnsupportedRequest *req = (struct UnsupportedRequest *)request_buffer; + req->has_response = 0; + req->type = ACTION_UNSUPPORTED; + req->dp_Type = dp->dp_Type; + + write_req_and_wait_for_res(sizeof(struct UnsupportedRequest)); + + struct UnsupportedResponse *res = (struct UnsupportedResponse *)request_buffer; + dp->dp_Res1 = res->success ? DOSTRUE : DOSFALSE; + dp->dp_Res2 = res->error_code; + reply_packet(dp); +} + +void start(__reg("a0") struct DosPacket *startup_packet) +{ + SysBase = *(struct ExecBase **)4; + DOSBase = (struct DosLibrary *)OpenLibrary(DOSNAME, 0); + + mp = MyCreatePort(NULL, 0); + + startup_fs_handler(startup_packet); + + while (1) + { + WaitPort(mp); + struct StandardPacket *sp = (struct StandardPacket *)GetMsg(mp); + struct DosPacket *dp = (struct DosPacket *)(sp->sp_Msg.mn_Node.ln_Name); + + switch (dp->dp_Type) + { + case ACTION_LOCATE_OBJECT: action_locate_object(dp); break; + case ACTION_FREE_LOCK: action_free_lock(dp); break; + case ACTION_COPY_DIR: action_copy_dir(dp); break; + case ACTION_PARENT: action_parent(dp); break; + case ACTION_EXAMINE_OBJECT: action_examine_object(dp); break; + case ACTION_EXAMINE_NEXT: action_examine_next(dp); break; + case ACTION_EXAMINE_FH: action_examine_fh(dp); break; + + case ACTION_FINDUPDATE: action_findxxx(dp); break; + case ACTION_FINDINPUT: action_findxxx(dp); break; + case ACTION_FINDOUTPUT: action_findxxx(dp); break; + case ACTION_READ: action_read(dp); break; + case ACTION_WRITE: action_write(dp); break; + case ACTION_SEEK: action_seek(dp); break; + case ACTION_END: action_end(dp); break; + //case ACTION_TRUNCATE: action_truncate(dp); break; + + case ACTION_DELETE_OBJECT: action_delete_object(dp); break; + case ACTION_RENAME_OBJECT: action_rename_object(dp); break; + case ACTION_CREATE_DIR: action_create_dir(dp); break; + + case ACTION_SET_PROTECT: action_set_protect(dp); break; + case ACTION_SET_COMMENT: action_set_comment(dp); break; + //case ACTION_SET_DATE: action_set_date(dp); break; + + case ACTION_SAME_LOCK: action_same_lock(dp); break; + case ACTION_DISK_INFO: action_disk_info(dp); break; + case ACTION_INFO: action_info(dp); break; + + /* + case ACTION_CURRENT_VOLUME: action_current_volume(dp); break; + case ACTION_RENAME_DISK: action_rename_disk(dp); break; + + case ACTION_DIE: //action_die(dp); break; + case ACTION_MORE_CACHE: //action_more_cache(dp); break; + case ACTION_FLUSH: //action_flush(dp); break; + case ACTION_INHIBIT: //action_inhibit(dp); break; + case ACTION_WRITE_PROTECT: //action_write_protect(dp); break; + */ + + default: action_unsupported(dp); break; + } + } + + dbg("Shutting down\n"); + + // More cleaning up is necessary if the handler is to exit. + CloseLibrary((struct Library *)DOSBase); +} diff --git a/a314/software-amiga/a314fs_pistorm/bcpl_end.asm b/a314/software-amiga/a314fs_pistorm/bcpl_end.asm new file mode 100644 index 0000000..5459133 --- /dev/null +++ b/a314/software-amiga/a314fs_pistorm/bcpl_end.asm @@ -0,0 +1,5 @@ + cnop 0,4 + dc.l 0 + dc.l 1 + dc.l 4 + dc.l 1 diff --git a/a314/software-amiga/a314fs_pistorm/bcpl_start.asm b/a314/software-amiga/a314fs_pistorm/bcpl_start.asm new file mode 100644 index 0000000..748c350 --- /dev/null +++ b/a314/software-amiga/a314fs_pistorm/bcpl_start.asm @@ -0,0 +1,8 @@ + dc.l 0 + + movem.l a0-a6,-(a7) + lsl.l #2,d1 + move.l d1,a0 + bsr _start + movem.l (a7)+,a0-a6 + jmp (a6) diff --git a/a314/software-amiga/a314fs_pistorm/build.bat b/a314/software-amiga/a314fs_pistorm/build.bat new file mode 100644 index 0000000..505cf2b --- /dev/null +++ b/a314/software-amiga/a314fs_pistorm/build.bat @@ -0,0 +1,5 @@ +vc a314fs.c -S -o a314fs.asm +python comment_out_sections.py +vc bcpl_start.asm a314fs.asm bcpl_end.asm -nostdlib -o ../a314fs +python patch_a314fs.py +del a314fs.asm diff --git a/a314/software-amiga/a314fs_pistorm/comment_out_sections.py b/a314/software-amiga/a314fs_pistorm/comment_out_sections.py new file mode 100644 index 0000000..b769e11 --- /dev/null +++ b/a314/software-amiga/a314fs_pistorm/comment_out_sections.py @@ -0,0 +1,5 @@ +with open('a314fs.asm', 'r+b') as f: + text = f.read().decode('utf-8') + text = text.replace('section', ';section') + f.seek(0, 0) + f.write(text.encode('utf-8')) diff --git a/a314/software-amiga/a314fs_pistorm/messages.h b/a314/software-amiga/a314fs_pistorm/messages.h new file mode 100644 index 0000000..da0050c --- /dev/null +++ b/a314/software-amiga/a314fs_pistorm/messages.h @@ -0,0 +1,318 @@ +#pragma pack(push, 1) + +struct LocateObjectRequest +{ + short has_response; + short type; + long key; + short mode; + char name[1]; +}; + +struct LocateObjectResponse +{ + short has_response; + short success; + short error_code; + long key; +}; + +struct FreeLockRequest +{ + short has_response; + short type; + long key; +}; + +struct FreeLockResponse +{ + short has_response; + short success; + short error_code; +}; + +struct CopyDirRequest +{ + short has_response; + short type; + long key; +}; + +struct CopyDirResponse +{ + short has_response; + short success; + short error_code; + long key; +}; + +struct ParentRequest +{ + short has_response; + short type; + long key; +}; + +struct ParentResponse +{ + short has_response; + short success; + short error_code; + long key; +}; + +struct ExamineObjectRequest +{ + short has_response; + short type; + long key; +}; + +struct ExamineObjectResponse +{ + short has_response; + short success; + short error_code; + + short disk_key; + short entry_type; + int size; + int protection; + int date[3]; + char data[1]; +}; + +struct ExamineNextRequest +{ + short has_response; + short type; + long key; + short disk_key; +}; + +struct ExamineNextResponse +{ + short has_response; + short success; + short error_code; + + short disk_key; + short entry_type; + int size; + int protection; + int date[3]; + char data[1]; +}; + +struct FindXxxRequest +{ + short has_response; + short type; + long key; + char name[1]; +}; + +struct FindXxxResponse +{ + short has_response; + short success; + short error_code; + long arg1; +}; + +struct ReadRequest +{ + short has_response; + short type; + long arg1; + int address; + int length; +}; + +struct ReadResponse +{ + short has_response; + short success; + short error_code; + int actual; +}; + +struct WriteRequest +{ + short has_response; + short type; + long arg1; + int address; + int length; +}; + +struct WriteResponse +{ + short has_response; + short success; + short error_code; + int actual; +}; + +struct SeekRequest +{ + short has_response; + short type; + long arg1; + int new_pos; + int mode; +}; + +struct SeekResponse +{ + short has_response; + short success; + short error_code; + int old_pos; +}; + +struct EndRequest +{ + short has_response; + short type; + long arg1; +}; + +struct EndResponse +{ + short has_response; + short success; + short error_code; +}; + +struct DeleteObjectRequest +{ + short has_response; + short type; + long key; + char name[1]; +}; + +struct DeleteObjectResponse +{ + short has_response; + short success; + short error_code; +}; + +struct RenameObjectRequest +{ + short has_response; + short type; + long key; + long target_dir; + unsigned char name_len; + unsigned char new_name_len; +}; + +struct RenameObjectResponse +{ + short has_response; + short success; + short error_code; +}; + +struct CreateDirRequest +{ + short has_response; + short type; + long key; + char name[1]; +}; + +struct CreateDirResponse +{ + short has_response; + short success; + short error_code; + long key; +}; + +struct SetProtectRequest +{ + short has_response; + short type; + long key; + long mask; + char name[1]; +}; + +struct SetProtectResponse +{ + short has_response; + short success; + short error_code; +}; + +struct SetCommentRequest +{ + short has_response; + short type; + long key; + unsigned char name_len; + unsigned char comment_len; +}; + +struct SetCommentResponse +{ + short has_response; + short success; + short error_code; +}; + +struct SameLockRequest +{ + short has_response; + short type; + long key1; + long key2; +}; + +struct SameLockResponse +{ + short has_response; + short success; + short error_code; +}; + +struct ExamineFhRequest +{ + short has_response; + short type; + long arg1; +}; + +struct ExamineFhResponse +{ + short has_response; + short success; + short error_code; + + short disk_key; + short entry_type; + int size; + int protection; + int date[3]; + char data[1]; +}; + +struct UnsupportedRequest +{ + short has_response; + short type; + short dp_Type; +}; + +struct UnsupportedResponse +{ + short has_response; + short success; + short error_code; +}; + +#pragma pack(pop) diff --git a/a314/software-amiga/a314fs_pistorm/patch_a314fs.py b/a314/software-amiga/a314fs_pistorm/patch_a314fs.py new file mode 100644 index 0000000..5f89126 --- /dev/null +++ b/a314/software-amiga/a314fs_pistorm/patch_a314fs.py @@ -0,0 +1,5 @@ +with open('../a314fs', 'r+b') as f: + f.seek(0x1c) + b = f.read(4) + f.seek(0x20) + f.write(b) diff --git a/a314/software-amiga/eth-config-amiga/A314Eth b/a314/software-amiga/eth-config-amiga/A314Eth new file mode 100644 index 0000000..e6d385e --- /dev/null +++ b/a314/software-amiga/eth-config-amiga/A314Eth @@ -0,0 +1,3 @@ +device=a314eth.device +address=192.168.2.2 +netmask=255.255.255.0 diff --git a/a314/software-amiga/eth-config-amiga/name_resolution b/a314/software-amiga/eth-config-amiga/name_resolution new file mode 100644 index 0000000..cae093a --- /dev/null +++ b/a314/software-amiga/eth-config-amiga/name_resolution @@ -0,0 +1 @@ +nameserver 8.8.8.8 diff --git a/a314/software-amiga/eth-config-amiga/routes b/a314/software-amiga/eth-config-amiga/routes new file mode 100644 index 0000000..2a3655d --- /dev/null +++ b/a314/software-amiga/eth-config-amiga/routes @@ -0,0 +1 @@ +default 192.168.2.1 diff --git a/a314/software-amiga/ethernet_pistorm/README.md b/a314/software-amiga/ethernet_pistorm/README.md new file mode 100644 index 0000000..59af112 --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/README.md @@ -0,0 +1,34 @@ +# a314eth.device - SANA-II driver for A314 + +**NOTE: This readme is the default A314 Ethernet driver readme, and some of these things don't apply to the PiStorm A314 emulation adapted binary.** +This SANA-II driver works by copying Ethernet frames back and forth between the Amiga and a virtual ethernet interface (tap0) on the Raspberry Pi. The Pi will, when configured properly, do network address translation (NAT) and route packets from the Amiga to the Internet. + +## Configuring the Raspberry Pi + +- Install pytun: `sudo pip3 install python-pytun`. +- Copy `ethernet.py` to `/opt/a314/ethernet.py`. +- Update `/etc/opt/a314/a314d.conf` with a line that starts `ethernet.py` on demand. + - In order for `a314d` to pick up the changes in `a314d.conf` you'll have to restart `a314d`, either by `sudo systemctl restart a314d` or by rebooting the Pi. +- Copy `pi-config/tap0` to `/etc/network/interfaces.d/tap0`. This file creates a tap device with ip address 192.168.2.1 when the Raspberry Pi is booted up. +- Add the lines in `pi-config/rc.local` to the bottom of `/etc/rc.local` just before `exit 0`. This create iptables rules that forwards packets from the `tap0` interface to the `wlan0` interface. + - Please note that if the Pi is connected using wired ethernet then `wlan0` should be changed to `eth0`. + +The first four steps are performed by `sudo make install`. The last step you have to do manually. + +## Configuring the Amiga + +This has only been tested with the Roadshow TCP/IP stack, and these instructions are written for Roadshow. The instructions won't describe how to install Roadshow. + +- Build the `a314eth.device` binary, for example using the `rpi_docker_build.sh` script. +- Copy `bin/a314eth.device` to `DEVS:`. +- Copy `amiga-config/A314Eth` to `DEVS:NetInterfaces/A314Eth`. +- Copy `amiga-config/routes` to `DEVS:Internet/routes`. +- Copy `amiga-config/name_resolution` to `DEVS:Internet/name_resolution`. + - You should change the nameserver to a DNS server that works on your network. + - Note that there may be settings in the above two files (`routes` and `name_resolution`) that you wish to keep, so look through the changes you are about to make first. + +Reboot the Amiga and with some luck you should be able to access the Internet from your Amiga. + +## Important note: + +After `ethernet.py` starts it waits for 15 seconds before it starts forwarding Ethernet frames. Without this waiting there is something that doesn't work properly (I don't know why this is). So when the Amiga boots for the first time after power off you'll have to wait up to 15 seconds before the Amiga can reach the Internet. diff --git a/a314/software-amiga/ethernet_pistorm/build.sh b/a314/software-amiga/ethernet_pistorm/build.sh new file mode 100644 index 0000000..4e511d4 --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/build.sh @@ -0,0 +1 @@ +m68k-amigaos-gcc device-2.c -O2 -o ../a314eth.device -m68000 -Wall -Wextra -Wno-unused-parameter -fomit-frame-pointer -nostartfiles -lm -ldebug diff --git a/a314/software-amiga/ethernet_pistorm/device-2.c b/a314/software-amiga/ethernet_pistorm/device-2.c new file mode 100644 index 0000000..af83be4 --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/device-2.c @@ -0,0 +1,845 @@ +/* + * Copyright (c) 2020-2021 Niklas Ekström + * + * Thanks to Christian Vogelgsang and Mike Sterling for inspiration gained from their SANA-II drivers: + * - https://github.com/cnvogelg/plipbox + * - https://github.com/mikestir/k1208-drivers + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +#include "../../a314device/a314.h" +#include "../../a314device/proto_a314.h" +#include "sana2.h" + +#include + +// Defines. + +#define kprintf(...) + +#define STR(s) #s +#define XSTR(s) STR(s) + +#define DEVICE_NAME "a314eth.device" +#define DEVICE_DATE "(19 May 2021)" +#define SERVICE_NAME "ethernet" +#define DEVICE_ID_STRING "A314Eth " XSTR(DEVICE_VERSION) "." XSTR(DEVICE_REVISION) " " DEVICE_DATE +#define DEVICE_VERSION 42 +#define DEVICE_REVISION 2 +#define DEVICE_PRIORITY 0 + + +#define DEVICE_NAME "a314eth.device" +#define SERVICE_NAME "ethernet" + +#define TASK_PRIO 10 + +#define MACADDR_SIZE 6 +#define NIC_BPS 10000000 + +#define ETH_MTU 1500 +#define RAW_MTU 1518 + +#define READ_FRAME_REQ 1 +#define WRITE_FRAME_REQ 2 +#define READ_FRAME_RES 3 +#define WRITE_FRAME_RES 4 + +#define ET_RBUF_CNT 2 +#define ET_WBUF_CNT 2 +#define ET_BUF_CNT (ET_RBUF_CNT + ET_WBUF_CNT) + +int __attribute__((no_reorder)) _start() +{ + return -1; +} + +asm("romtag: \n" + " dc.w "XSTR(RTC_MATCHWORD)" \n" + " dc.l romtag \n" + " dc.l endcode \n" + " dc.b "XSTR(RTF_AUTOINIT)" \n" + " dc.b "XSTR(DEVICE_VERSION)" \n" + " dc.b "XSTR(NT_DEVICE)" \n" + " dc.b "XSTR(DEVICE_PRIORITY)" \n" + " dc.l _device_name \n" + " dc.l _device_id_string \n" + " dc.l _auto_init_tables \n" + "endcode: \n" + ".align 4\n" + " dc.l 16\n" + "_device_process_seglist:\n" + " dc.l 0\n" + " jmp _device_process_run\n"); + +char device_name[] = DEVICE_NAME; +char device_id_string[] = DEVICE_ID_STRING; + +// Typedefs. + +typedef BOOL (*buf_copy_func_t)(void *dst asm("a0"), void *src asm("a1"), LONG size asm("d0")); + +// Structs. + +#pragma pack(push, 1) +struct EthHdr +{ + unsigned char eh_Dst[MACADDR_SIZE]; + unsigned char eh_Src[MACADDR_SIZE]; + unsigned short eh_Type; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct ServiceMsg +{ + ULONG sm_Address; + UWORD sm_Length; + UWORD sm_Kind; +}; +#pragma pack(pop) + +struct BufDesc +{ + struct MinNode bd_Node; + void *bd_Buffer; + int bd_Length; +}; + +// Constants. + +static const char service_name[] = SERVICE_NAME; +static const char a314_device_name[] = A314_NAME; + +static const unsigned char macaddr[MACADDR_SIZE] = { 0x40, 0x61, 0x33, 0x31, 0x34, 0x65 }; + +// Global variables. + +BPTR saved_seg_list; +struct ExecBase *SysBase; +struct DosLibrary *DOSBase; +struct Library *A314Base; + +buf_copy_func_t copyfrom; +buf_copy_func_t copyto; + +volatile struct List ut_rbuf_list; +volatile struct List ut_wbuf_list; + +struct BufDesc et_bufs[ET_BUF_CNT]; + +struct List et_rbuf_free_list; +struct List et_rbuf_pending_list; +struct List et_rbuf_has_data_list; + +struct List et_wbuf_free_list; +struct List et_wbuf_pending_list; + +LONG a314_socket; +short last_req_kind; + +struct MsgPort a314_mp; + +struct A314_IORequest read_ior; +struct A314_IORequest write_ior; +struct A314_IORequest reset_ior; + +BOOL pending_a314_read; +BOOL pending_a314_write; +BOOL pending_a314_reset; + +struct ServiceMsg a314_read_buf; +struct ServiceMsg a314_write_buf; + +volatile ULONG sana2_sigmask; +volatile ULONG shutdown_sigmask; + +volatile struct Task *init_task; + +struct Process *device_process; +volatile int device_start_error; +void device_process_run(); + +// External declarations. + +extern void device_process_seglist(); + +// Procedures. + +static struct Library __attribute__((used)) *init_device(uint8_t *seg_list asm("a0"), struct Library *dev asm("d0")) +{ + saved_seg_list = (BPTR)seg_list; + + dev->lib_Node.ln_Type = NT_DEVICE; + dev->lib_Node.ln_Name = (char *)device_name; + dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED; + dev->lib_Version = 1; + dev->lib_Revision = 0; + dev->lib_IdString = (APTR)device_id_string; + + SysBase = *(struct ExecBase **)4; + DOSBase = (struct DosLibrary *)OpenLibrary((STRPTR)DOSNAME, 0); + + return dev; +} + +static uint8_t* __attribute__((used)) expunge(struct Library *dev asm("a6")) +{ + if (dev->lib_OpenCnt) + { + dev->lib_Flags |= LIBF_DELEXP; + return 0; + } + + // Shady way of waiting for device process to terminate before unloading. + Delay(10); + + CloseLibrary((struct Library *)DOSBase); + + Remove(&dev->lib_Node); + FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize); + return (uint8_t *)saved_seg_list; +} + +static void send_a314_cmd(struct A314_IORequest *ior, UWORD cmd, char *buffer, int length) +{ + ior->a314_Request.io_Command = cmd; + ior->a314_Request.io_Error = 0; + ior->a314_Socket = a314_socket; + ior->a314_Buffer = (STRPTR)buffer; + ior->a314_Length = length; + SendIO((struct IORequest *)ior); +} + +static void do_a314_cmd(struct A314_IORequest *ior, UWORD cmd, char *buffer, int length) +{ + ior->a314_Request.io_Command = cmd; + ior->a314_Request.io_Error = 0; + ior->a314_Socket = a314_socket; + ior->a314_Buffer = (STRPTR)buffer; + ior->a314_Length = length; + DoIO((struct IORequest *)ior); +} + +static void copy_from_bd_and_reply(struct IOSana2Req *ios2, struct BufDesc *bd) +{ + struct EthHdr *eh = bd->bd_Buffer; + + if (ios2->ios2_Req.io_Flags & SANA2IOF_RAW) + { + ios2->ios2_DataLength = bd->bd_Length; + copyto(ios2->ios2_Data, bd->bd_Buffer, ios2->ios2_DataLength); + ios2->ios2_Req.io_Flags = SANA2IOF_RAW; + } + else + { + ios2->ios2_DataLength = bd->bd_Length - sizeof(struct EthHdr); + copyto(ios2->ios2_Data, &eh[1], ios2->ios2_DataLength); + ios2->ios2_Req.io_Flags = 0; + } + + memcpy(ios2->ios2_SrcAddr, eh->eh_Src, MACADDR_SIZE); + memcpy(ios2->ios2_DstAddr, eh->eh_Dst, MACADDR_SIZE); + + BOOL bcast = TRUE; + for (int i = 0; i < MACADDR_SIZE; i++) + { + if (eh->eh_Dst[i] != 0xff) + { + bcast = FALSE; + break; + } + } + + if (bcast) + ios2->ios2_Req.io_Flags |= SANA2IOF_BCAST; + + ios2->ios2_PacketType = eh->eh_Type; + + ios2->ios2_Req.io_Error = 0; + ReplyMsg(&ios2->ios2_Req.io_Message); +} + +static void copy_to_bd_and_reply(struct BufDesc *bd, struct IOSana2Req *ios2) +{ + struct EthHdr *eh = bd->bd_Buffer; + + if (ios2->ios2_Req.io_Flags & SANA2IOF_RAW) + { + copyfrom(bd->bd_Buffer, ios2->ios2_Data, ios2->ios2_DataLength); + bd->bd_Length = ios2->ios2_DataLength; + } + else + { + eh->eh_Type = ios2->ios2_PacketType; + memcpy(eh->eh_Src, macaddr, sizeof(macaddr)); + memcpy(eh->eh_Dst, ios2->ios2_DstAddr, MACADDR_SIZE); + copyfrom(&eh[1], ios2->ios2_Data, ios2->ios2_DataLength); + bd->bd_Length = ios2->ios2_DataLength + sizeof(struct EthHdr); + } + + ios2->ios2_Req.io_Error = 0; + ReplyMsg(&ios2->ios2_Req.io_Message); +} + +static void handle_a314_reply(struct A314_IORequest *ior) +{ + if (ior == &write_ior) + { + pending_a314_write = FALSE; + + if (ior->a314_Request.io_Error == A314_WRITE_OK) + { + // Start new write later. + } + else // A314_WRITE_RESET + { + // TODO: Handle. What if pi-side is shutting down. + } + } + else if (ior == &read_ior) + { + pending_a314_read = FALSE; + + if (ior->a314_Request.io_Error == A314_READ_OK) + { + if (a314_read_buf.sm_Kind == WRITE_FRAME_RES) + { + struct BufDesc *bd = (struct BufDesc *)RemHead(&et_wbuf_pending_list); + AddTail(&et_wbuf_free_list, (struct Node *)bd); + } + else // READ_FRAME_RES + { + struct BufDesc *bd = (struct BufDesc *)RemHead(&et_rbuf_pending_list); + bd->bd_Length = a314_read_buf.sm_Length; + AddTail(&et_rbuf_has_data_list, (struct Node *)bd); + } + + send_a314_cmd(&read_ior, A314_READ, (void *)&a314_read_buf, sizeof(a314_read_buf)); + pending_a314_read = TRUE; + } + else // A314_READ_RESET + { + // TODO: Handle. What if pi-side is shutting down. + } + } + else if (ior == &reset_ior) + { + pending_a314_reset = FALSE; + } +} + +static struct IOSana2Req *remove_matching_rbuf(ULONG type) +{ + struct Node *node = ut_rbuf_list.lh_Head; + while (node->ln_Succ) + { + struct IOSana2Req *ios2 = (struct IOSana2Req *)node; + if (ios2->ios2_PacketType == type) + { + Remove(node); + return ios2; + } + node = node->ln_Succ; + } + return NULL; +} + +static void complete_read_reqs() +{ + struct Node *node = et_rbuf_has_data_list.lh_Head; + if (!node->ln_Succ) + return; + + Forbid(); + while (node->ln_Succ) + { + struct BufDesc *bd = (struct BufDesc *)node; + struct EthHdr *eh = (struct EthHdr *)bd->bd_Buffer; + + node = node->ln_Succ; + + struct IOSana2Req *ios2 = remove_matching_rbuf(eh->eh_Type); + if (ios2) + { + copy_from_bd_and_reply(ios2, bd); + + Remove((struct Node *)bd); + AddTail(&et_rbuf_free_list, (struct Node *)bd); + } + } + Permit(); +} + +static void maybe_write_req() +{ + if (pending_a314_write) + return; + + BOOL free_et_wbuf = et_wbuf_free_list.lh_Head->ln_Succ != NULL; + BOOL idle_et_rbuf = et_rbuf_free_list.lh_Head->ln_Succ != NULL; + + Forbid(); + + BOOL waiting_ut_wbuf = ut_wbuf_list.lh_Head->ln_Succ != NULL; + + BOOL want_wbuf = free_et_wbuf && waiting_ut_wbuf; + BOOL want_rbuf = idle_et_rbuf; + + if (!want_rbuf && !want_wbuf) + { + Permit(); + return; + } + + short next_req_kind = 0; + + if (last_req_kind == WRITE_FRAME_REQ) + next_req_kind = want_rbuf ? READ_FRAME_REQ : WRITE_FRAME_REQ; + else + next_req_kind = want_wbuf ? WRITE_FRAME_REQ : READ_FRAME_REQ; + + struct IOSana2Req *ios2 = NULL; + if (next_req_kind == WRITE_FRAME_REQ) + ios2 = (struct IOSana2Req*)RemHead((struct List *)&ut_wbuf_list); + + Permit(); + + struct BufDesc *bd; + + if (next_req_kind == READ_FRAME_REQ) + { + bd = (struct BufDesc *)RemHead(&et_rbuf_free_list); + bd->bd_Length = RAW_MTU; + AddTail(&et_rbuf_pending_list, (struct Node *)&bd->bd_Node); + } + else // WRITE_FRAME_REQ + { + bd = (struct BufDesc *)RemHead(&et_wbuf_free_list); + copy_to_bd_and_reply(bd, ios2); + AddTail(&et_wbuf_pending_list, (struct Node *)bd); + } + + a314_write_buf.sm_Address = TranslateAddressA314(bd->bd_Buffer); + a314_write_buf.sm_Length = bd->bd_Length; + a314_write_buf.sm_Kind = next_req_kind; + + send_a314_cmd(&write_ior, A314_WRITE, (void *)&a314_write_buf, sizeof(a314_write_buf)); + pending_a314_write = TRUE; + + last_req_kind = next_req_kind; +} + +void device_process_run() +{ + ULONG sana2_signal = AllocSignal(-1); + sana2_sigmask = 1UL << sana2_signal; + + ULONG shutdown_signal = AllocSignal(-1); + shutdown_sigmask = 1UL << shutdown_signal; + + a314_mp.mp_SigBit = AllocSignal(-1); + a314_mp.mp_SigTask = FindTask(NULL); + + do_a314_cmd(&reset_ior, A314_CONNECT, (char *)service_name, strlen(service_name)); + device_start_error = reset_ior.a314_Request.io_Error == A314_CONNECT_OK ? 0 : -1; + + Signal((struct Task *)init_task, SIGF_SINGLE); + + if (device_start_error) + return; + + ULONG a314_sigmask = 1UL << a314_mp.mp_SigBit; + + send_a314_cmd(&read_ior, A314_READ, (void *)&a314_read_buf, sizeof(a314_read_buf)); + pending_a314_read = TRUE; + + BOOL shutting_down = FALSE; + + while (TRUE) + { + complete_read_reqs(); + maybe_write_req(); + + if (shutting_down && !pending_a314_read && !pending_a314_write && !pending_a314_reset) + break; + + ULONG sigs = Wait(a314_sigmask | sana2_sigmask | shutdown_sigmask); + + if ((sigs & shutdown_sigmask) && !shutting_down) + { + send_a314_cmd(&reset_ior, A314_RESET, NULL, 0); + pending_a314_reset = TRUE; + shutting_down = TRUE; + } + + if (sigs & a314_sigmask) + { + struct A314_IORequest *ior; + while ((ior = (struct A314_IORequest *)GetMsg(&a314_mp))) + handle_a314_reply(ior); + } + } + + Signal((struct Task *)init_task, SIGF_SINGLE); +} + +static struct TagItem *FindTagItem(Tag tagVal, struct TagItem *tagList) +{ + struct TagItem *ti = tagList; + while (ti && ti->ti_Tag != tagVal) + { + switch (ti->ti_Tag) + { + case TAG_DONE: + return NULL; + case TAG_MORE: + ti = (struct TagItem *)ti->ti_Data; + break; + case TAG_SKIP: + ti += ti->ti_Data + 1; + break; + case TAG_IGNORE: + default: + ti++; + break; + } + } + return ti; +} + +static ULONG GetTagData(Tag tagVal, ULONG defaultData, struct TagItem *tagList) +{ + struct TagItem *ti = FindTagItem(tagVal, tagList); + return ti ? ti->ti_Data : defaultData; +} + +static void __attribute__((used)) open(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1"), uint32_t unitnum asm("d0"), uint32_t flags asm("d1")) +{ + kprintf("We opening this shit.\n"); + ios2->ios2_Req.io_Error = IOERR_OPENFAIL; + ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + + if (unitnum != 0 || dev->lib_OpenCnt) + return; + + dev->lib_OpenCnt++; + + kprintf("Try the copyfrom crap.\n"); + copyfrom = (buf_copy_func_t)GetTagData(S2_CopyFromBuff, 0, (struct TagItem *)ios2->ios2_BufferManagement); + copyto = (buf_copy_func_t)GetTagData(S2_CopyToBuff, 0, (struct TagItem *)ios2->ios2_BufferManagement); + ios2->ios2_BufferManagement = (void *)0xdeadbeefUL; + + kprintf("Memsetting some shit.\n"); + memset(&a314_mp, 0, sizeof(a314_mp)); + a314_mp.mp_Node.ln_Pri = 0; + a314_mp.mp_Node.ln_Type = NT_MSGPORT; + a314_mp.mp_Node.ln_Name = (char *)device_name; + a314_mp.mp_Flags = PA_SIGNAL; + NewList(&a314_mp.mp_MsgList); + + kprintf("Memsetting more shit.\n"); + memset(&write_ior, 0, sizeof(write_ior)); + write_ior.a314_Request.io_Message.mn_ReplyPort = &a314_mp; + write_ior.a314_Request.io_Message.mn_Length = sizeof(write_ior); + write_ior.a314_Request.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + + kprintf("Opendevice.\n"); + A314Base = NULL; + if (OpenDevice((STRPTR)a314_device_name, 0, (struct IORequest *)&write_ior, 0)) + goto error; + + A314Base = &(write_ior.a314_Request.io_Device->dd_Library); + + kprintf("Copy memory.\n"); + memcpy(&read_ior, &write_ior, sizeof(read_ior)); + memcpy(&reset_ior, &write_ior, sizeof(reset_ior)); + + kprintf("Making datestamps.\n"); + struct DateStamp ds; + DateStamp(&ds); + a314_socket = (ds.ds_Minute * 60 * TICKS_PER_SECOND) + ds.ds_Tick; + + last_req_kind = WRITE_FRAME_REQ; + + kprintf("Making lists.\n"); + NewList((struct List *)&ut_rbuf_list); + NewList((struct List *)&ut_wbuf_list); + + NewList(&et_rbuf_free_list); + NewList(&et_rbuf_pending_list); + NewList(&et_rbuf_has_data_list); + + NewList(&et_wbuf_free_list); + NewList(&et_wbuf_pending_list); + + kprintf("Memzero buffers.\n"); + for (int i = 0; i < ET_BUF_CNT; i++) + memset(&et_bufs[i], 0, sizeof(struct BufDesc)); + + kprintf("Add tails.\n"); + for (int i = 0; i < ET_BUF_CNT; i++) + { + struct BufDesc *bd = &et_bufs[i]; + + bd->bd_Buffer = AllocMem(RAW_MTU, MEMF_FAST); + if (!bd->bd_Buffer) + goto error; + + if (i < ET_RBUF_CNT) + AddTail(&et_rbuf_free_list, (struct Node*)&bd->bd_Node); + else + AddTail(&et_wbuf_free_list, (struct Node*)&bd->bd_Node); + } + + kprintf("Find task.\n"); + init_task = FindTask(NULL); + + kprintf("Do msgport.\n"); + struct MsgPort *device_mp = CreateProc((STRPTR)device_name, TASK_PRIO, ((ULONG)&device_process_seglist) >> 2, 2048); + if (!device_mp) + goto error; + + kprintf("Process thing.\n"); + device_process = (struct Process *)((char *)device_mp - sizeof(struct Task)); + + kprintf("Waitf.\n"); + Wait(SIGF_SINGLE); + kprintf("Waitedf.\n"); + + if (device_start_error) { + kprintf("Device start error.\n"); + goto error; + } + + kprintf("Everything ok?\n"); + ios2->ios2_Req.io_Error = 0; + return; + +error: + kprintf("Error small farts.\n"); + for (int i = ET_BUF_CNT - 1; i >= 0; i--) + if (et_bufs[i].bd_Buffer) + FreeMem(et_bufs[i].bd_Buffer, RAW_MTU); + + if (A314Base) + { + CloseDevice((struct IORequest *)&write_ior); + A314Base = NULL; + } + + dev->lib_OpenCnt--; +} + +static uint8_t* __attribute__((used)) close(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1")) +{ + kprintf("Close.\n"); + init_task = FindTask(NULL); + Signal(&device_process->pr_Task, shutdown_sigmask); + Wait(SIGF_SINGLE); + + for (int i = ET_BUF_CNT - 1; i >= 0; i--) + FreeMem(et_bufs[i].bd_Buffer, RAW_MTU); + + CloseDevice((struct IORequest *)&write_ior); + A314Base = NULL; + + ios2->ios2_Req.io_Device = NULL; + ios2->ios2_Req.io_Unit = NULL; + + dev->lib_OpenCnt--; + + if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP)) + return expunge(dev); + + return 0; +} + +static void device_query(struct IOSana2Req *req) +{ + struct Sana2DeviceQuery *query; + + query = req->ios2_StatData; + query->DevQueryFormat = 0; + query->DeviceLevel = 0; + + if (query->SizeAvailable >= 18) + query->AddrFieldSize = MACADDR_SIZE * 8; + + if (query->SizeAvailable >= 22) + query->MTU = ETH_MTU; + + if (query->SizeAvailable >= 26) + query->BPS = NIC_BPS; + + if (query->SizeAvailable >= 30) + query->HardwareType = S2WireType_Ethernet; + + query->SizeSupplied = query->SizeAvailable < 30 ? query->SizeAvailable : 30; +} + +static void __attribute__((used)) begin_io(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1")) +{ + kprintf("BeginIO.\n"); + ios2->ios2_Req.io_Error = S2ERR_NO_ERROR; + ios2->ios2_WireError = S2WERR_GENERIC_ERROR; + + switch (ios2->ios2_Req.io_Command) + { + case CMD_READ: + if (!ios2->ios2_BufferManagement) + { + ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT; + ios2->ios2_WireError = S2WERR_BUFF_ERROR; + break; + } + + Forbid(); + AddTail((struct List *)&ut_rbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + Permit(); + + ios2->ios2_Req.io_Flags &= ~SANA2IOF_QUICK; + ios2 = NULL; + + Signal(&device_process->pr_Task, sana2_sigmask); + break; + + case S2_BROADCAST: + memset(ios2->ios2_DstAddr, 0xff, MACADDR_SIZE); + /* Fall through */ + + case CMD_WRITE: + if (((ios2->ios2_Req.io_Flags & SANA2IOF_RAW) != 0 && ios2->ios2_DataLength > RAW_MTU) || + ((ios2->ios2_Req.io_Flags & SANA2IOF_RAW) == 0 && ios2->ios2_DataLength > ETH_MTU)) + { + ios2->ios2_Req.io_Error = S2ERR_MTU_EXCEEDED; + break; + } + + if (!ios2->ios2_BufferManagement) + { + ios2->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT; + ios2->ios2_WireError = S2WERR_BUFF_ERROR; + break; + } + + Forbid(); + AddTail((struct List *)&ut_wbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + Permit(); + + ios2->ios2_Req.io_Flags &= ~SANA2IOF_QUICK; + ios2 = NULL; + + Signal(&device_process->pr_Task, sana2_sigmask); + break; + + case S2_ONLINE: + case S2_OFFLINE: + case S2_CONFIGINTERFACE: + break; + case S2_GETSTATIONADDRESS: + memcpy(ios2->ios2_SrcAddr, macaddr, sizeof(macaddr)); + memcpy(ios2->ios2_DstAddr, macaddr, sizeof(macaddr)); + break; + case S2_DEVICEQUERY: + device_query(ios2); + break; + + case S2_ONEVENT: + case S2_TRACKTYPE: + case S2_UNTRACKTYPE: + case S2_GETTYPESTATS: + case S2_READORPHAN: + case S2_GETGLOBALSTATS: + case S2_GETSPECIALSTATS: + break; + + default: + ios2->ios2_Req.io_Error = IOERR_NOCMD; + ios2->ios2_WireError = S2WERR_GENERIC_ERROR; + break; + } + + if (ios2) + { + if (ios2->ios2_Req.io_Flags & SANA2IOF_QUICK) + ios2->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE; + else + ReplyMsg(&ios2->ios2_Req.io_Message); + } +} + +static void remove_from_list(struct List *list, struct Node *node) +{ + for (struct Node *n = list->lh_Head; n->ln_Succ; n = n->ln_Succ) + { + if (n == node) + { + Remove(n); + return; + } + } +} + +static uint32_t __attribute__((used)) abort_io(struct Library *dev asm("a6"), struct IOSana2Req *ios2 asm("a1")) +{ + kprintf("AbortIO.\n"); + Forbid(); + remove_from_list((struct List *)&ut_rbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + remove_from_list((struct List *)&ut_wbuf_list, &ios2->ios2_Req.io_Message.mn_Node); + Permit(); + + ios2->ios2_Req.io_Error = IOERR_ABORTED; + ios2->ios2_WireError = 0; + ReplyMsg(&ios2->ios2_Req.io_Message); + + return 0; +} + +static ULONG device_vectors[] = +{ + (ULONG)open, + (ULONG)close, + (ULONG)expunge, + 0, + (ULONG)begin_io, + (ULONG)abort_io, + -1, +}; + +ULONG auto_init_tables[] = +{ + sizeof(struct Library), + (ULONG)device_vectors, + 0, + (ULONG)init_device, +}; diff --git a/a314/software-amiga/ethernet_pistorm/sana2.h b/a314/software-amiga/ethernet_pistorm/sana2.h new file mode 100644 index 0000000..645a0cd --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/sana2.h @@ -0,0 +1,261 @@ +#ifndef SANA2_SANA2DEVICE_H +#define SANA2_SANA2DEVICE_H 1 +/* +** $Filename: devices/sana2.h $ +** $Revision: 4.1 $ +** $Date: 1994/10/03 20:55:10 $ +** +** Structure definitions for SANA-II devices. +** +** (C) Copyright 1991 Commodore-Amiga Inc. +** All Rights Reserved +*/ + + +#ifndef EXEC_TYPES_H +#include +#endif + +#ifndef EXEC_PORTS_H +#include +#endif + +#ifndef EXEC_IO_H +#include +#endif + +#ifndef EXEC_ERRORS_H +#include +#endif + +#ifndef DEVICES_TIMER_H +#include +#endif + +#ifndef UTILITY_TAGITEM_H +#include "tagitem.h" +#endif + + +#define SANA2_MAX_ADDR_BITS (128) +#define SANA2_MAX_ADDR_BYTES ((SANA2_MAX_ADDR_BITS+7)/8) + +struct IOSana2Req +{ + struct IORequest ios2_Req; + ULONG ios2_WireError; /* wire type specific error */ + ULONG ios2_PacketType; /* packet type */ + UBYTE ios2_SrcAddr[SANA2_MAX_ADDR_BYTES]; /* source addr */ + UBYTE ios2_DstAddr[SANA2_MAX_ADDR_BYTES]; /* dest address */ + ULONG ios2_DataLength; /* length of packet data */ + VOID *ios2_Data; /* packet data */ + VOID *ios2_StatData; /* statistics data pointer */ + VOID *ios2_BufferManagement; /* see SANA-II OpenDevice adoc */ +}; + + +/* +** defines for the io_Flags field +*/ +#define SANA2IOB_RAW (7) /* raw packet IO requested */ +#define SANA2IOF_RAW (1<) +*/ +#define S2ERR_NO_ERROR 0 /* peachy-keen */ +#define S2ERR_NO_RESOURCES 1 /* resource allocation failure */ +#define S2ERR_BAD_ARGUMENT 3 /* garbage somewhere */ +#define S2ERR_BAD_STATE 4 /* inappropriate state */ +#define S2ERR_BAD_ADDRESS 5 /* who? */ +#define S2ERR_MTU_EXCEEDED 6 /* too much to chew */ +#define S2ERR_NOT_SUPPORTED 8 /* hardware can't support cmd */ +#define S2ERR_SOFTWARE 9 /* software error detected */ +#define S2ERR_OUTOFSERVICE 10 /* driver is OFFLINE */ +#define S2ERR_TX_FAILURE 11 /* Transmission attempt failed */ +/* +** From +** +** IOERR_OPENFAIL (-1) * device/unit failed to open * +** IOERR_ABORTED (-2) * request terminated early [after AbortIO()] * +** IOERR_NOCMD (-3) * command not supported by device * +** IOERR_BADLENGTH (-4) * not a valid length (usually IO_LENGTH) * +** IOERR_BADADDRESS (-5) * invalid address (misaligned or bad range) * +** IOERR_UNITBUSY (-6) * device opens ok, but requested unit is busy * +** IOERR_SELFTEST (-7) * hardware failed self-test * +*/ + +/* +** defined errors for ios2_WireError +*/ +#define S2WERR_GENERIC_ERROR 0 /* no specific info available */ +#define S2WERR_NOT_CONFIGURED 1 /* unit not configured */ +#define S2WERR_UNIT_ONLINE 2 /* unit is currently online */ +#define S2WERR_UNIT_OFFLINE 3 /* unit is currently offline */ +#define S2WERR_ALREADY_TRACKED 4 /* protocol already tracked */ +#define S2WERR_NOT_TRACKED 5 /* protocol not tracked */ +#define S2WERR_BUFF_ERROR 6 /* buff mgt func returned error */ +#define S2WERR_SRC_ADDRESS 7 /* source address problem */ +#define S2WERR_DST_ADDRESS 8 /* destination address problem */ +#define S2WERR_BAD_BROADCAST 9 /* broadcast address problem */ +#define S2WERR_BAD_MULTICAST 10 /* multicast address problem */ +#define S2WERR_MULTICAST_FULL 11 /* multicast address list full */ +#define S2WERR_BAD_EVENT 12 /* unsupported event class */ +#define S2WERR_BAD_STATDATA 13 /* statdata failed sanity check */ +#define S2WERR_IS_CONFIGURED 15 /* attempt to config twice */ +#define S2WERR_NULL_POINTER 16 /* null pointer detected */ +#define S2WERR_TOO_MANY_RETIRES 17 /* tx failed - too many retries */ +#define S2WERR_RCVREL_HDW_ERR 18 /* Driver fixable HW error */ + + +/* +** defined events +*/ +#define S2EVENT_ERROR (1L<<0) /* error catch all */ +#define S2EVENT_TX (1L<<1) /* transmitter error catch all */ +#define S2EVENT_RX (1L<<2) /* receiver error catch all */ +#define S2EVENT_ONLINE (1L<<3) /* unit is in service */ +#define S2EVENT_OFFLINE (1L<<4) /* unit is not in service */ +#define S2EVENT_BUFF (1L<<5) /* buff mgt function error */ +#define S2EVENT_HARDWARE (1L<<6) /* hardware error catch all */ +#define S2EVENT_SOFTWARE (1L<<7) /* software error catch all */ + + +#endif /* SANA2_SANA2DEVICE_H */ diff --git a/a314/software-amiga/ethernet_pistorm/tagitem.h b/a314/software-amiga/ethernet_pistorm/tagitem.h new file mode 100644 index 0000000..15e05cc --- /dev/null +++ b/a314/software-amiga/ethernet_pistorm/tagitem.h @@ -0,0 +1,77 @@ +#ifndef UTILITY_TAGITEM_H +#define UTILITY_TAGITEM_H +/* +** $VER: tagitem.h 40.1 (19.7.1993) +** Includes Release 45.1 +** +** Extended specification mechanism +** +** (C) Copyright 1989-2001 Amiga, Inc. +** All Rights Reserved +*/ + +/*****************************************************************************/ + + +#ifndef EXEC_TYPES_H +#include +#endif + + +/*****************************************************************************/ + + +/* Tags are a general mechanism of extensible data arrays for parameter + * specification and property inquiry. In practice, tags are used in arrays, + * or chain of arrays. + * + */ + +typedef ULONG Tag; + +struct TagItem +{ + Tag ti_Tag; /* identifies the type of data */ + ULONG ti_Data; /* type-specific data */ +}; + +/* constants for Tag.ti_Tag, control tag values */ +#define TAG_DONE (0L) /* terminates array of TagItems. ti_Data unused */ +#define TAG_END (0L) /* synonym for TAG_DONE */ +#define TAG_IGNORE (1L) /* ignore this item, not end of array */ +#define TAG_MORE (2L) /* ti_Data is pointer to another array of TagItems + * note that this tag terminates the current array + */ +#define TAG_SKIP (3L) /* skip this and the next ti_Data items */ + +/* differentiates user tags from control tags */ +#define TAG_USER ((ULONG)(1L<<31)) + +/* If the TAG_USER bit is set in a tag number, it tells utility.library that + * the tag is not a control tag (like TAG_DONE, TAG_IGNORE, TAG_MORE) and is + * instead an application tag. "USER" means a client of utility.library in + * general, including system code like Intuition or ASL, it has nothing to do + * with user code. + */ + + +/*****************************************************************************/ + + +/* Tag filter logic specifiers for use with FilterTagItems() */ +#define TAGFILTER_AND 0 /* exclude everything but filter hits */ +#define TAGFILTER_NOT 1 /* exclude only filter hits */ + + +/*****************************************************************************/ + + +/* Mapping types for use with MapTags() */ +#define MAP_REMOVE_NOT_FOUND 0 /* remove tags that aren't in mapList */ +#define MAP_KEEP_NOT_FOUND 1 /* keep tags that aren't in mapList */ + + +/*****************************************************************************/ + + +#endif /* UTILITY_TAGITEM_H */ diff --git a/a314/software-amiga/pi b/a314/software-amiga/pi new file mode 100644 index 0000000..474dabe Binary files /dev/null and b/a314/software-amiga/pi differ diff --git a/a314/software-amiga/pi_pistorm/build.bat b/a314/software-amiga/pi_pistorm/build.bat new file mode 100644 index 0000000..c042803 --- /dev/null +++ b/a314/software-amiga/pi_pistorm/build.bat @@ -0,0 +1 @@ +vc pi.c -lamiga -o ../pi diff --git a/a314/software-amiga/pi_pistorm/pi.c b/a314/software-amiga/pi_pistorm/pi.c new file mode 100644 index 0000000..2624f8a --- /dev/null +++ b/a314/software-amiga/pi_pistorm/pi.c @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2018-2021 Niklas Ekström + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "../../a314device/a314.h" +#include "../../a314device/proto_a314.h" + +#define PICMD_SERVICE_NAME "picmd" + +#define ID_314_DISK (('3' << 24) | ('1' << 16) | ('4' << 8)) + +struct MsgPort *sync_mp; +struct MsgPort *async_mp; + +struct A314_IORequest *read_ior; +struct A314_IORequest *sync_ior; + +struct Library *A314Base; + +struct FileHandle *con; + +ULONG socket; + +UBYTE arbuf[256]; + +struct StandardPacket sync_sp; +struct StandardPacket wait_sp; + +BOOL pending_a314_read = FALSE; +BOOL pending_con_wait = FALSE; +BOOL stream_closed = FALSE; + +ULONG a314_addr = 0xFFFFFFFF; + +//#define DEBUG printf +#define DEBUG(...) + +void put_con_sp(struct MsgPort *reply_port, struct StandardPacket *sp, LONG action, LONG arg1, LONG arg2, LONG arg3) +{ + sp->sp_Msg.mn_Node.ln_Type = NT_MESSAGE; + sp->sp_Msg.mn_Node.ln_Pri = 0; + sp->sp_Msg.mn_Node.ln_Name = (char *)&(sp->sp_Pkt); + sp->sp_Msg.mn_Length = sizeof(struct StandardPacket); + sp->sp_Msg.mn_ReplyPort = reply_port; + sp->sp_Pkt.dp_Link = &(sp->sp_Msg); + sp->sp_Pkt.dp_Port = reply_port; + sp->sp_Pkt.dp_Type = action; + sp->sp_Pkt.dp_Arg1 = arg1; + sp->sp_Pkt.dp_Arg2 = arg2; + sp->sp_Pkt.dp_Arg3 = arg3; + PutMsg(con->fh_Type, &(sp->sp_Msg)); +} + +LONG set_screen_mode(LONG mode) +{ + put_con_sp(sync_mp, &sync_sp, ACTION_SCREEN_MODE, mode, 0, 0); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_sp.sp_Pkt.dp_Res1; +} + +LONG con_write(char *s, int length) +{ + put_con_sp(sync_mp, &sync_sp, ACTION_WRITE, con->fh_Arg1, (LONG)s, length); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_sp.sp_Pkt.dp_Res1; +} + +LONG con_read(char *s, int length) +{ + put_con_sp(sync_mp, &sync_sp, ACTION_READ, con->fh_Arg1, (LONG)s, length); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_sp.sp_Pkt.dp_Res1; +} + +void start_con_wait() +{ + put_con_sp(async_mp, &wait_sp, ACTION_WAIT_CHAR, 100000, 0, 0); + pending_con_wait = TRUE; +} + +void start_a314_cmd(struct MsgPort *reply_port, struct A314_IORequest *ior, UWORD cmd, char *buffer, int length) +{ + ior->a314_Request.io_Message.mn_ReplyPort = reply_port; + ior->a314_Request.io_Command = cmd; + ior->a314_Request.io_Error = 0; + ior->a314_Socket = socket; + ior->a314_Buffer = buffer; + ior->a314_Length = length; + SendIO((struct IORequest *)ior); +} + +BYTE a314_connect(char *name) +{ + socket = time(NULL); + start_a314_cmd(sync_mp, sync_ior, A314_CONNECT, name, strlen(name)); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_ior->a314_Request.io_Error; +} + +BYTE a314_write(char *buffer, int length) +{ + ULONG *bef = (ULONG *)buffer; + DEBUG("Buf[0]: %.8X Buf[1]: %.8X\n", bef[0], bef[1]); + DEBUG("Len: %d\n", length); + start_a314_cmd(sync_mp, sync_ior, A314_WRITE, buffer, length); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_ior->a314_Request.io_Error; +} + +BYTE a314_eos() +{ + start_a314_cmd(sync_mp, sync_ior, A314_EOS, NULL, 0); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_ior->a314_Request.io_Error; +} + +BYTE a314_reset() +{ + start_a314_cmd(sync_mp, sync_ior, A314_RESET, NULL, 0); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_ior->a314_Request.io_Error; +} + +void start_a314_read() +{ + start_a314_cmd(async_mp, read_ior, A314_READ, arbuf, 255); + pending_a314_read = TRUE; +} + +void handle_con_wait_completed() +{ + DEBUG("handling con wait completed.\n"); + pending_con_wait = FALSE; + + if (stream_closed) + return; + + if (wait_sp.sp_Pkt.dp_Res1 == DOSFALSE) + { + start_con_wait(); + } + else + { + unsigned char buf[64]; + int len = con_read(buf, sizeof(buf)); + + if (len == 0 || len == -1) + { + a314_reset(); + stream_closed = TRUE; + } + else + { + a314_write(buf, len); + start_con_wait(); + } + } +} + +void handle_a314_read_completed() +{ + DEBUG("handling read completed.\n"); + pending_a314_read = FALSE; + + if (stream_closed) + return; + + int res = read_ior->a314_Request.io_Error; + if (res == A314_READ_OK) + { + UBYTE *p = read_ior->a314_Buffer; + int len = read_ior->a314_Length; + + con_write(p, len); + start_a314_read(); + } + else if (res == A314_READ_EOS) + { + a314_eos(); + stream_closed = TRUE; + } + else if (res == A314_READ_RESET) + { + stream_closed = TRUE; + } +} + +UBYTE *create_and_send_start_msg(int *buffer_len, BPTR current_dir, int argc, char **argv, short rows, short cols) +{ + int buf_len = 6; + + int component_count = 0; + UBYTE *components[20]; + + DEBUG("casmm: SetupDir\n"); + if (current_dir != 0) + { + struct FileLock *fl = (struct FileLock *)BADDR(current_dir); + struct DeviceList *dl = (struct DeviceList *)BADDR(fl->fl_Volume); + + if (dl->dl_DiskType == ID_314_DISK) + { + struct FileInfoBlock *fib = AllocMem(sizeof(struct FileInfoBlock), 0); + + BPTR lock = DupLock(current_dir); + + while (lock != 0) + { + if (Examine(lock, fib) == 0) + { + UnLock(lock); + break; + } + + int n = strlen(fib->fib_FileName); + UBYTE *p = AllocMem(n + 1, 0); + p[0] = (UBYTE)n; + memcpy(&p[1], fib->fib_FileName, n); + components[component_count++] = p; + + buf_len += n + 1; + + BPTR child = lock; + lock = ParentDir(child); + UnLock(child); + } + + FreeMem(fib, sizeof(struct FileInfoBlock)); + } + } + + DEBUG("casmm: Stage 2\n"); + for (int i = 1; i < argc; i++) + buf_len += strlen(argv[i]) + 1; + + UBYTE *buffer = AllocMem(buf_len, MEMF_FAST); + + UBYTE *p = buffer; + + *(short *)p = rows; + p += 2; + *(short *)p = cols; + p += 2; + + DEBUG("casmm: Stage 3\n"); + DEBUG("p: %.8X\n", (ULONG)p); + DEBUG("component count: %d\n", component_count); + *p++ = (UBYTE)component_count; + for (int i = 0; i < component_count; i++) + { + UBYTE *q = components[component_count - 1 - i]; + int n = *q; + memcpy(p, q, n + 1); + p += n + 1; + FreeMem(q, n + 1); + } + + DEBUG("casmm: Stage 4\n"); + *p++ = (UBYTE)(argc - 1); + for (int i = 1; i < argc; i++) + { + UBYTE *q = (UBYTE *)argv[i]; + int n = strlen(q); + *p++ = (UBYTE)n; + memcpy(p, q, n); + p += n; + } + + DEBUG("casmm: Stage 5\n"); + ULONG buf_desc[2] = {(ULONG)buffer, buf_len}; + DEBUG("casmm: Stage 6\n"); + a314_write((char *)buf_desc, sizeof(buf_desc)); + + DEBUG("casmm: Stage 7\n"); + *buffer_len = buf_len; + return buffer; +} + +int main(int argc, char **argv) +{ + ULONG board_addr = 0xFFFFFFFF; + struct ExpansionBase *ExpansionBase = (struct ExpansionBase *)OpenLibrary((STRPTR)"expansion.library", 0L); + + if (ExpansionBase == NULL) { + printf("Failed to open expansion.library.\n"); + return 0; + } + else { + struct ConfigDev* cd = NULL; + cd = (struct ConfigDev*)FindConfigDev(cd, 2011, 0xA3); + if (cd != NULL) + board_addr = (unsigned int)cd->cd_BoardAddr; + else { + printf ("Failed to find A314 emulation device.\n"); + CloseLibrary((struct Library *)ExpansionBase); + return 0; + } + CloseLibrary((struct Library *)ExpansionBase); + } + printf ("A314 emulation device found at $%.8X\n", board_addr); + a314_addr = board_addr; + + sync_mp = CreatePort(NULL, 0); + if (sync_mp == NULL) + { + printf("Unable to create sync reply message port\n"); + return 0; + } + DEBUG("Created sync reply message port.\n"); + + async_mp = CreatePort(NULL, 0); + if (async_mp == NULL) + { + printf("Unable to create async reply message port\n"); + DeletePort(sync_mp); + return 0; + } + DEBUG("Created async reply message port.\n"); + + sync_ior = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest)); + if (sync_ior == NULL) + { + printf("Unable to create io request for synchronous commands\n"); + DeletePort(async_mp); + DeletePort(sync_mp); + return 0; + } + DEBUG("Created IORequest for synchronous commands.\n"); + + read_ior = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest)); + if (read_ior == NULL) + { + printf("Unable to create io request for reads\n"); + DeleteExtIO((struct IORequest *)sync_ior); + DeletePort(async_mp); + DeletePort(sync_mp); + return 0; + } + DEBUG("Created IORequest for reads.\n"); + + if (OpenDevice(A314_NAME, 0, (struct IORequest *)sync_ior, 0) != 0) + { + printf("Unable to open a314.device\n"); + DeleteExtIO((struct IORequest *)read_ior); + DeleteExtIO((struct IORequest *)sync_ior); + DeletePort(async_mp); + DeletePort(sync_mp); + return 0; + } + DEBUG("Opened a314.device.\n"); + + memcpy(read_ior, sync_ior, sizeof(struct A314_IORequest)); + + A314Base = &(sync_ior->a314_Request.io_Device->dd_Library); + + if (a314_connect(PICMD_SERVICE_NAME) != A314_CONNECT_OK) + { + printf("Unable to connect to picmd service\n"); + CloseDevice((struct IORequest *)sync_ior); + DeleteExtIO((struct IORequest *)read_ior); + DeleteExtIO((struct IORequest *)sync_ior); + DeletePort(async_mp); + DeletePort(sync_mp); + return 0; + } + DEBUG("Connected to picmd service.\n"); + + struct Process *proc = (struct Process *)FindTask(NULL); + con = (struct FileHandle *)BADDR(proc->pr_CIS); + + set_screen_mode(DOSTRUE); + DEBUG("Set screen mode.\n"); + + con_write("\x9b" "0 q", 4); + + int len = con_read(arbuf, 32); // "\x9b" "1;1;33;77 r" + if (len < 10 || arbuf[len - 1] != 'r') + { + printf("Failure to receive window bounds report\n"); + set_screen_mode(DOSFALSE); + a314_reset(); + CloseDevice((struct IORequest *)sync_ior); + DeleteExtIO((struct IORequest *)read_ior); + DeleteExtIO((struct IORequest *)sync_ior); + DeletePort(async_mp); + DeletePort(sync_mp); + return 0; + } + DEBUG("Received window bounds report.\n"); + + con_write("\x9b" "12{", 4); + + int start = 5; + int ind = start; + while (arbuf[ind] != ';') + ind++; + arbuf[ind] = 0; + int rows = atoi(arbuf + start); + ind++; + start = ind; + while (arbuf[ind] != ' ') + ind++; + arbuf[ind] = 0; + int cols = atoi(arbuf + start); + + int start_msg_len; + DEBUG("Sending start message.\n"); + UBYTE *start_msg = create_and_send_start_msg(&start_msg_len, proc->pr_CurrentDir, argc, argv, (short)rows, (short)cols); + DEBUG("Sent start message.\n"); + + DEBUG("Started con wait.\n"); + start_con_wait(); + DEBUG("Started A314 read.\n"); + start_a314_read(); + + ULONG portsig = 1L << async_mp->mp_SigBit; + + DEBUG("Entering main loop.\n"); + while (TRUE) + { + ULONG signal = Wait(portsig | SIGBREAKF_CTRL_C); + + if (signal & portsig) + { + struct Message *msg; + while (msg = GetMsg(async_mp)) + { + if (msg == (struct Message *)&wait_sp) + handle_con_wait_completed(); + else if (msg == (struct Message *)read_ior) + handle_a314_read_completed(); + } + } + + if (stream_closed && !pending_a314_read && !pending_con_wait) + break; + } + + set_screen_mode(DOSFALSE); + + FreeMem(start_msg, start_msg_len); + + CloseDevice((struct IORequest *)sync_ior); + DeleteExtIO((struct IORequest *)read_ior); + DeleteExtIO((struct IORequest *)sync_ior); + DeletePort(async_mp); + DeletePort(sync_mp); + return 0; +} diff --git a/a314/software-amiga/piaudio b/a314/software-amiga/piaudio new file mode 100644 index 0000000..a50b7bb Binary files /dev/null and b/a314/software-amiga/piaudio differ diff --git a/a314/software-amiga/piaudio_pistorm/README.md b/a314/software-amiga/piaudio_pistorm/README.md new file mode 100644 index 0000000..8e26ed0 --- /dev/null +++ b/a314/software-amiga/piaudio_pistorm/README.md @@ -0,0 +1,13 @@ +# PiAudio + +PiAudio is a service that integrates with ALSA, the sound sub-system on the Raspberry Pi, and lets sound samples be played via Paula on the Amiga. + +The *piaudio* program should run on the Amiga. It allocates two sounds channels, one left and one right. Using the following command the piaudio program can run in the background: +``` +run piaudio >NIL: +``` + +The file *.asoundrc* should be stored in `/home/pi` on the RPi. Most programs that play audio on the RPi can then be used. One such program is mpg123, which is started as: +``` +mpg123 -a amiga song.mp3 +``` diff --git a/a314/software-amiga/piaudio_pistorm/build.bat b/a314/software-amiga/piaudio_pistorm/build.bat new file mode 100644 index 0000000..9cde638 --- /dev/null +++ b/a314/software-amiga/piaudio_pistorm/build.bat @@ -0,0 +1 @@ +vc piaudio.c -lamiga -o ../piaudio diff --git a/a314/software-amiga/piaudio_pistorm/piaudio.c b/a314/software-amiga/piaudio_pistorm/piaudio.c new file mode 100644 index 0000000..82c23fd --- /dev/null +++ b/a314/software-amiga/piaudio_pistorm/piaudio.c @@ -0,0 +1,319 @@ +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +#include "../../a314device/a314.h" +#include "../../a314device/proto_a314.h" + +#include + +#define SERVICE_NAME "piaudio" + +#define FREQ 18000 +#define BUFFER_LEN_MS 50 +#define SAMPLES (FREQ * BUFFER_LEN_MS / 1000) + +#define R0 1 +#define L0 2 +#define L1 4 +#define R1 8 + +#define LEFT_CHAN_MASK (L0 | L1) +#define RIGHT_CHAN_MASK (R0 | R1) + +#define LEFT 0 +#define RIGHT 1 + +struct MsgPort *sync_mp = NULL; +struct MsgPort *async_mp = NULL; + +struct A314_IORequest *sync_a314_req = NULL; +struct A314_IORequest *write_a314_req = NULL; + +struct Library *A314Base; + +char *audio_buffers[4] = { NULL, NULL, NULL, NULL }; + +struct IOAudio *sync_audio_req = NULL; +struct IOAudio *async_audio_req[4] = { NULL, NULL, NULL, NULL }; + +ULONG allocated_channels; + +BOOL a314_device_open = FALSE; +BOOL audio_device_open = FALSE; +BOOL stream_open = FALSE; +BOOL pending_a314_write = FALSE; + +ULONG socket; +int back_index = 0; +char awbuf[8]; + +void start_a314_cmd(struct MsgPort *reply_port, struct A314_IORequest *ior, UWORD cmd, char *buffer, int length) +{ + ior->a314_Request.io_Message.mn_ReplyPort = reply_port; + ior->a314_Request.io_Command = cmd; + ior->a314_Request.io_Error = 0; + ior->a314_Socket = socket; + ior->a314_Buffer = buffer; + ior->a314_Length = length; + SendIO((struct IORequest *)ior); +} + +BYTE a314_connect(char *name) +{ + socket = time(NULL); + start_a314_cmd(sync_mp, sync_a314_req, A314_CONNECT, name, strlen(name)); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_a314_req->a314_Request.io_Error; +} + +BYTE a314_write(char *buffer, int length) +{ + start_a314_cmd(sync_mp, sync_a314_req, A314_WRITE, buffer, length); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_a314_req->a314_Request.io_Error; +} + +BYTE a314_eos() +{ + start_a314_cmd(sync_mp, sync_a314_req, A314_EOS, NULL, 0); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_a314_req->a314_Request.io_Error; +} + +BYTE a314_reset() +{ + start_a314_cmd(sync_mp, sync_a314_req, A314_RESET, NULL, 0); + Wait(1L << sync_mp->mp_SigBit); + GetMsg(sync_mp); + return sync_a314_req->a314_Request.io_Error; +} + +void start_a314_write(char *buffer, int length) +{ + start_a314_cmd(async_mp, write_a314_req, A314_WRITE, buffer, length); + pending_a314_write = TRUE; +} + +void submit_async_audio_req(int index) +{ + ULONG mask = ((index & 1) == LEFT) ? LEFT_CHAN_MASK : RIGHT_CHAN_MASK; + ULONG unit = allocated_channels & mask; + + async_audio_req[index]->ioa_Request.io_Message.mn_ReplyPort = async_mp; + async_audio_req[index]->ioa_Request.io_Command = CMD_WRITE; + async_audio_req[index]->ioa_Request.io_Flags = ADIOF_PERVOL; + async_audio_req[index]->ioa_Request.io_Unit = (void*)unit; + async_audio_req[index]->ioa_Data = audio_buffers[index]; + async_audio_req[index]->ioa_Length = SAMPLES; + async_audio_req[index]->ioa_Period = 197; + async_audio_req[index]->ioa_Volume = 64; + async_audio_req[index]->ioa_Cycles = 1; + BeginIO((struct IORequest *)async_audio_req[index]); +} + +int main() +{ + SetTaskPri(FindTask(NULL), 50); + + sync_mp = CreatePort(NULL, 0); + if (!sync_mp) + { + printf("Unable to create sync reply message port\n"); + goto cleanup; + } + + async_mp = CreatePort(NULL, 0); + if (!async_mp) + { + printf("Unable to create async reply message port\n"); + goto cleanup; + } + + sync_a314_req = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest)); + write_a314_req = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest)); + if (!sync_a314_req || !write_a314_req) + { + printf("Unable to create A314_IORequest\n"); + goto cleanup; + } + + if (OpenDevice(A314_NAME, 0, (struct IORequest *)sync_a314_req, 0)) + { + printf("Unable to open a314.device\n"); + goto cleanup; + } + + a314_device_open = TRUE; + + A314Base = &(sync_a314_req->a314_Request.io_Device->dd_Library); + + memcpy(write_a314_req, sync_a314_req, sizeof(struct A314_IORequest)); + + audio_buffers[0] = AllocMem(SAMPLES * 2, MEMF_FAST | MEMF_CHIP | MEMF_CLEAR); + audio_buffers[2] = AllocMem(SAMPLES * 2, MEMF_FAST | MEMF_CHIP | MEMF_CLEAR); + if (!audio_buffers[0] || !audio_buffers[2]) + { + printf("Unable to allocate audio buffers in A314 chip memory\n"); + goto cleanup; + } + + audio_buffers[1] = audio_buffers[0] + SAMPLES; + audio_buffers[3] = audio_buffers[2] + SAMPLES; + + sync_audio_req = (struct IOAudio *)CreateExtIO(sync_mp, sizeof(struct IOAudio)); + if (!sync_audio_req) + { + printf("Unable to allocate sync audio request\n"); + goto cleanup; + } + + int i; + for (i = 0; i < 4; i++) + { + async_audio_req[i] = AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC); + if (!async_audio_req[i]) + { + printf("Unable to allocate async audio request\n"); + goto cleanup; + } + } + + UBYTE which_channels[] = { L0 | R0, L0 | R1, L1 | R0, L1 | R1 }; + + sync_audio_req->ioa_Request.io_Message.mn_ReplyPort = sync_mp; + sync_audio_req->ioa_Request.io_Message.mn_Node.ln_Pri = 127; + sync_audio_req->ioa_Request.io_Command = ADCMD_ALLOCATE; + sync_audio_req->ioa_Request.io_Flags = ADIOF_NOWAIT; + sync_audio_req->ioa_AllocKey = 0; + sync_audio_req->ioa_Data = which_channels; + sync_audio_req->ioa_Length = sizeof(which_channels); + + if (OpenDevice(AUDIONAME, 0, (struct IORequest *)sync_audio_req, 0)) + { + printf("Unable to open audio.device\n"); + goto cleanup; + } + + audio_device_open = TRUE; + + allocated_channels = (ULONG)sync_audio_req->ioa_Request.io_Unit; + + for (i = 0; i < 4; i++) + memcpy(async_audio_req[i], sync_audio_req, sizeof(struct IOAudio)); + + if (a314_connect(SERVICE_NAME) != A314_CONNECT_OK) + { + printf("Unable to connect to piaudio service\n"); + goto cleanup; + } + + stream_open = TRUE; + + ULONG *buf_ptrs = (ULONG *)awbuf; + buf_ptrs[0] = TranslateAddressA314(audio_buffers[0]); + buf_ptrs[1] = TranslateAddressA314(audio_buffers[2]); + if (a314_write(awbuf, 8) != A314_WRITE_OK) + { + printf("Unable to write buffer pointers\n"); + goto cleanup; + } + + printf("PiAudio started, allocated channels: L%d, R%d\n", + (allocated_channels & LEFT_CHAN_MASK) == L0 ? 0 : 1, + (allocated_channels & RIGHT_CHAN_MASK) == R0 ? 0 : 1); + + sync_audio_req->ioa_Request.io_Command = CMD_STOP; + DoIO((struct IORequest *)sync_audio_req); + + submit_async_audio_req(back_index + LEFT); + submit_async_audio_req(back_index + RIGHT); + + sync_audio_req->ioa_Request.io_Command = CMD_START; + DoIO((struct IORequest *)sync_audio_req); + + int pending_audio_reqs = 2; + + ULONG portsig = 1L << async_mp->mp_SigBit; + + printf("Press ctrl-c to exit...\n"); + + while (TRUE) + { + if (pending_audio_reqs <= 2) + { + back_index ^= 2; + + submit_async_audio_req(back_index + LEFT); + submit_async_audio_req(back_index + RIGHT); + + pending_audio_reqs += 2; + + if (!pending_a314_write) + { + awbuf[0] = back_index == 0 ? 0 : 1; + start_a314_write(awbuf, 1); + } + } + + ULONG signal = Wait(SIGBREAKF_CTRL_C | portsig); + + if (signal & SIGBREAKF_CTRL_C) + break; + else if (signal & portsig) + { + struct Message *msg; + while (msg = GetMsg(async_mp)) + { + if (msg == (struct Message *)write_a314_req) + { + if (write_a314_req->a314_Request.io_Error == A314_WRITE_OK) + pending_a314_write = FALSE; + else + goto cleanup; + } + else + pending_audio_reqs--; + } + } + } + +cleanup: + if (stream_open) + a314_reset(); + if (audio_device_open) + CloseDevice((struct IORequest *)sync_audio_req); + for (i = 3; i >= 0; i--) + if (async_audio_req[i]) + FreeMem(async_audio_req[i], sizeof(struct IOAudio)); + if (sync_audio_req) + DeleteExtIO((struct IORequest *)sync_audio_req); + if (audio_buffers[2]) + FreeMem(audio_buffers[2], SAMPLES * 2); + if (audio_buffers[0]) + FreeMem(audio_buffers[0], SAMPLES * 2); + if (a314_device_open) + CloseDevice((struct IORequest *)sync_a314_req); + if (write_a314_req) + DeleteExtIO((struct IORequest *)write_a314_req); + if (sync_a314_req) + DeleteExtIO((struct IORequest *)sync_a314_req); + if (async_mp) + DeletePort(async_mp); + if (sync_mp) + DeletePort(sync_mp); + + return 0; +} diff --git a/boot_scripts/README.md b/boot_scripts/README.md new file mode 100644 index 0000000..e12a598 --- /dev/null +++ b/boot_scripts/README.md @@ -0,0 +1,104 @@ +# PiStorm on Pi Boot + +## Bootup script + +For your convience a startup script for systemd in Linux is located in this directory. This script will start PiStorm *before* the network connections have started. + +### Installation + +To start PiStorm on automatically on boot, copy `pistorm.service` into /etc/systemd/system/ in your Pi's filesystem. Then run: + +```bash +sudo systemctl enable pistorm +``` +### Customisation + +There are some things you may want to change in the systemd script. These instructions will help with that. + +#### Custom config + +If you wish to boot using a custom configuration file change `ExecStart` to: + +```ini +ExecStart=/home/pi/pistorm/emulator --config-file myconfig.cfg +``` + +Where `myconfig.cfg` is your config file in the `pistorm` directory. If your config file is somewhere else you will need to put the full path for it there. + +**NOTE:** do not put an '=' between `--config-file` and the file name, this will not work. + +#### Different location + +You may want to run your PiStorm from a different location than `/home/pi/pistorm` this is fine, but it is important that the files that come with the emulator stay together in the same directory structure. For example, if you moved pistorm to `/opt/pistorm` you will need to change the following two lines: + +```ini +ExecStart=/opt/pistorm/emulator +WorkingDirectory=/opt/pistorm +``` + +It is important both lines are changed otherwise this can cause issues, particularly crashes. + +#### Startup order + +If, for example, your PiStorm configuration depends on something like Samba running for PiScsi you will want to change the startup order so the PiStorm waits for that to run. In the `[Unit]` second add something like the following example: + +```ini +After=network.target samba.service +``` + +Please see the systemd documentation for more informatino on this. + +### Applying changes + +If you have made any changes to the `pistorm.service` file *after* it has been copied over you will need to reload the systemd configuration for the changes to be seen. This can be done with: + +```bash +sudo systemctl daemon-reload +``` + +## Faster boot + +The Pi does several things that aren't needed for PiStorm at bootup, the following steps will accelerate the boot time for you. + +### config.txt + +Edit `/boot/config.txt` and add the following lines: + +```ini +# Disable the rainbow splash screen +disable_splash=1 + +# Set the bootloader delay to 0 seconds. The default is 1s if not specified. +boot_delay=0 + +# Disable Bluetooth +dtoverlay=disable-bt +``` + +By default there is a 1 second boot delay and initialising bluetooth takes a couple of seconds. + +### cmdline.txt + +Edit the `/boot/cmdline.txt` and add `quiet` near the end, as an example (do **NOT** copy/paste this line): + +``` +console=serial0,115200 console=tty1 root=PARTUUID=5f1219ae-02 rootfstype=ext4 elevator=deadline fsck.repair=yes quiet rootwait +``` + +This shaves a little time off spitting out boot logs to the screen. + +### fstab + +Disable `/boot` remount on boot, this will mean you will need to do `sudo mount /boot` when you want to change files in that partition, but it shaves off half a second from boot. To do this add `noauto` to the options second for the `/boot` line, for example (do **NOT** copy/paste this line): + +``` +PARTUUID=5f1219ae-01 /boot vfat defaults,noauto 0 2 +``` + +### Disable swap + +If we need swap something went wrong. This shaves about another second off the boot time: + +```bash +sudo systemctl disable dphys-swapfile.service +``` diff --git a/boot_scripts/pistorm.service b/boot_scripts/pistorm.service new file mode 100644 index 0000000..e9ce22b --- /dev/null +++ b/boot_scripts/pistorm.service @@ -0,0 +1,15 @@ +[Unit] +Description=PiStorm emulator +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=root +ExecStart=/home/pi/pistorm/emulator +WorkingDirectory=/home/pi/pistorm + +[Install] +WantedBy=multi-user.target + diff --git a/build_buptest.sh b/build_buptest.sh old mode 100644 new mode 100755 diff --git a/buptest.c b/buptest.c index b2cd77c..428b2e9 100644 --- a/buptest.c +++ b/buptest.c @@ -24,7 +24,8 @@ #define SIZE_MEGA (1024 * 1024) #define SIZE_GIGA (1024 * 1024 * 1024) -uint8_t garbege_datas[2 * SIZE_MEGA]; +uint8_t *garbege_datas; +extern volatile unsigned int *gpio; struct timespec f2; @@ -42,6 +43,61 @@ void sigint_handler(int sig_num) { exit(0); } +void ps_reinit() { + ps_reset_state_machine(); + ps_pulse_reset(); + + usleep(1500); + + write8(0xbfe201, 0x0101); //CIA OVL + write8(0xbfe001, 0x0000); //CIA OVL LOW +} + +unsigned int dump_read_8(unsigned int address) { + uint32_t bwait = 0; + + *(gpio + 0) = GPFSEL0_OUTPUT; + *(gpio + 1) = GPFSEL1_OUTPUT; + *(gpio + 2) = GPFSEL2_OUTPUT; + + *(gpio + 7) = ((address & 0xffff) << 8) | (REG_ADDR_LO << PIN_A0); + *(gpio + 7) = 1 << PIN_WR; + *(gpio + 10) = 1 << PIN_WR; + *(gpio + 10) = 0xffffec; + + *(gpio + 7) = ((0x0300 | (address >> 16)) << 8) | (REG_ADDR_HI << PIN_A0); + *(gpio + 7) = 1 << PIN_WR; + *(gpio + 10) = 1 << PIN_WR; + *(gpio + 10) = 0xffffec; + + *(gpio + 0) = GPFSEL0_INPUT; + *(gpio + 1) = GPFSEL1_INPUT; + *(gpio + 2) = GPFSEL2_INPUT; + + *(gpio + 7) = (REG_DATA << PIN_A0); + *(gpio + 7) = 1 << PIN_RD; + + + while (bwait < 10000 && (*(gpio + 13) & (1 << PIN_TXN_IN_PROGRESS))) { + bwait++; + } + + unsigned int value = *(gpio + 13); + + *(gpio + 10) = 0xffffec; + + value = (value >> 8) & 0xffff; + + if (bwait == 10000) { + ps_reinit(); + } + + if ((address & 1) == 0) + return (value >> 8) & 0xff; // EVEN, A0=0,UDS + else + return value & 0xff; // ODD , A0=1,LDS +} + int main(int argc, char *argv[]) { uint32_t test_size = 512 * SIZE_KILO, cur_loop = 0; @@ -60,6 +116,46 @@ int main(int argc, char *argv[]) { write8(0xbfe001, 0x0000); //CIA OVL LOW if (argc > 1) { + if (strcmp(argv[1], "dumpkick") == 0) { + printf ("Dumping onboard Amiga kickstart from $F80000 to file kick.rom.\n"); + printf ("Note that this will always dump 512KB of data, even if your Kickstart is 256KB.\n"); + FILE *out = fopen("kick.rom", "wb+"); + if (out == NULL) { + printf ("Failed to open kick.rom for writing.\nKickstart has not been dumped.\n"); + return 1; + } + + for (int i = 0; i < 512 * SIZE_KILO; i++) { + unsigned char in = read8(0xF80000 + i); + fputc(in, out); + } + + fclose(out); + printf ("Amiga Kickstart ROM dumped.\n"); + return 0; + } + + if (strcmp(argv[1], "dump") == 0) { + printf ("Dumping EVERYTHING to dump.bin.\n"); + FILE *out = fopen("dump.bin", "wb+"); + if (out == NULL) { + printf ("Failed to open dump.bin for writing.\nEverything has not been dumped.\n"); + return 1; + } + + for (int i = 0; i < 16 * SIZE_MEGA; i++) { + unsigned char in = dump_read_8(i); + fputc(in, out); + if (!(i % 1024)) + printf ("."); + } + printf ("\n"); + + fclose(out); + printf ("Dumped everything.\n"); + return 0; + } + test_size = atoi(argv[1]) * SIZE_KILO; if (test_size == 0 || test_size > 2 * SIZE_MEGA) { test_size = 512 * SIZE_KILO; @@ -73,6 +169,12 @@ int main(int argc, char *argv[]) { } } + garbege_datas = malloc(test_size); + if (!garbege_datas) { + printf ("Failed to allocate memory for garbege datas.\n"); + return 1; + } + test_loop:; printf("Writing garbege datas.\n"); for (uint32_t i = 0; i < test_size; i++) { diff --git a/config_file/config_file.c b/config_file/config_file.c index dab3508..d60b9b7 100644 --- a/config_file/config_file.c +++ b/config_file/config_file.c @@ -5,6 +5,8 @@ #include #include +#include "rominfo.h" + #define M68K_CPU_TYPES M68K_CPU_TYPE_SCC68070 const char *cpu_types[M68K_CPU_TYPES] = { @@ -25,6 +27,7 @@ const char *map_type_names[MAPTYPE_NUM] = { "rom", "ram", "register", + "ram (no alloc)", }; const char *config_item_names[CONFITEM_NUM] = { @@ -48,6 +51,8 @@ const char *mapcmd_names[MAPCMD_NUM] = { "file", "ovl", "id", + "autodump_file", + "autodump_mem", }; int get_config_item_type(char *cmd) { @@ -142,7 +147,7 @@ unsigned int get_int(char *str) { } void get_next_string(char *str, char *str_out, int *strpos, char separator) { - int str_pos = 0, out_pos = 0; + int str_pos = 0, out_pos = 0, startquote = 0, endstring = 0; if (!str_out) return; @@ -150,19 +155,36 @@ void get_next_string(char *str, char *str_out, int *strpos, char separator) { if (strpos) str_pos = *strpos; - while (str[str_pos] == ' ' && str[str_pos] == '\t' && str_pos < (int)strlen(str)) { + while ((str[str_pos] == ' ' || str[str_pos] == '\t') && str_pos < (int)strlen(str)) { + str_pos++; + } + + if (str[str_pos] == '\"') { str_pos++; + startquote = 1; } + for (int i = str_pos; i < (int)strlen(str); i++) { str_out[out_pos] = str[i]; - if ((separator == ' ' && (str[i] == ' ' || str[i] == '\t')) || str[i] == separator) { + + if (startquote) { + if (str[i] == '\"') + endstring = 1; + } else { + if ((separator == ' ' && (str[i] == ' ' || str[i] == '\t')) || str[i] == separator) { + endstring = 1; + } + } + + if (endstring) { str_out[out_pos] = '\0'; if (strpos) { *strpos = i + 1; } break; } + out_pos++; if (i + 1 == (int)strlen(str) && strpos) { *strpos = i + 1; @@ -171,7 +193,7 @@ void get_next_string(char *str, char *str_out, int *strpos, char separator) { } } -void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int addr, unsigned int size, int mirr_addr, char *filename, char *map_id) { +void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int addr, unsigned int size, int mirr_addr, char *filename, char *map_id, unsigned int autodump) { unsigned int index = 0, file_size = 0; FILE *in = NULL; @@ -196,6 +218,10 @@ void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int ad } switch(type) { + case MAPTYPE_RAM_NOALLOC: + printf("[CFG] Adding %d byte (%d MB) RAM mapping %s...\n", size, size / 1024 / 1024, map_id); + cfg->map_data[index] = (unsigned char *)filename; + break; case MAPTYPE_RAM: printf("[CFG] Allocating %d bytes for RAM mapping (%d MB)...\n", size, size / 1024 / 1024); cfg->map_data[index] = (unsigned char *)malloc(size); @@ -208,8 +234,27 @@ void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int ad case MAPTYPE_ROM: in = fopen(filename, "rb"); if (!in) { - printf("[CFG] Failed to open file %s for ROM mapping.\n", filename); - goto mapping_failed; + if (!autodump) { + printf("[CFG] Failed to open file %s for ROM mapping. Using onboard ROM instead, if available.\n", filename); + goto mapping_failed; + } else if (autodump == MAPCMD_AUTODUMP_FILE) { + printf("[CFG] Could not open file %s for ROM mapping. Autodump flag is set, dumping to file.\n", filename); + dump_range_to_file(cfg->map_offset[index], cfg->map_size[index], filename); + in = fopen(filename, "rb"); + if (in == NULL) { + printf("[CFG] Could not open dumped file for reading. Using onboard ROM instead, if available.\n"); + goto mapping_failed; + } + } else if (autodump == MAPCMD_AUTODUMP_MEM) { + printf("[CFG] Could not open file %s for ROM mapping. Autodump flag is set, dumping to memory.\n", filename); + cfg->map_data[index] = dump_range_to_memory(cfg->map_offset[index], cfg->map_size[index]); + cfg->rom_size[index] = cfg->map_size[index]; + if (cfg->map_data[index] == NULL) { + printf("[CFG] Could not dump range to memory. Using onboard ROM instead, if available.\n"); + goto mapping_failed; + } + goto skip_file_ops; + } } fseek(in, 0, SEEK_END); file_size = (int)ftell(in); @@ -226,7 +271,12 @@ void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int ad } memset(cfg->map_data[index], 0x00, cfg->map_size[index]); fread(cfg->map_data[index], cfg->rom_size[index], 1, in); - fclose(in); + if (in) + fclose(in); +skip_file_ops: + displayRomInfo(cfg->map_data[index], cfg->rom_size[index]); + if (cfg->map_size[index] == cfg->rom_size[index]) + m68k_add_rom_range(cfg->map_offset[index], cfg->map_high[index], cfg->map_data[index]); break; case MAPTYPE_REGISTER: default: @@ -234,8 +284,6 @@ void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int ad } printf("[CFG] [MAP %d] Added %s mapping for range %.8lX-%.8lX ID: %s\n", index, map_type_names[type], cfg->map_offset[index], cfg->map_high[index] - 1, cfg->map_id[index] ? cfg->map_id[index] : "None"); - if (cfg->map_size[index] == cfg->rom_size[index]) - m68k_add_rom_range(cfg->map_offset[index], cfg->map_high[index], cfg->map_data[index]); return; @@ -245,6 +293,41 @@ void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int ad fclose(in); } +void free_config_file(struct emulator_config *cfg) { + if (!cfg) { + printf("[CFG] Tried to free NULL config, aborting.\n"); + } + + if (cfg->platform) { + cfg->platform->shutdown(cfg); + free(cfg->platform); + cfg->platform = NULL; + } + + for (int i = 0; i < MAX_NUM_MAPPED_ITEMS; i++) { + if (cfg->map_data[i]) { + free(cfg->map_data[i]); + cfg->map_data[i] = NULL; + } + if (cfg->map_id[i]) { + free(cfg->map_id[i]); + cfg->map_id[i] = NULL; + } + } + if (cfg->mouse_file) { + free(cfg->mouse_file); + cfg->mouse_file = NULL; + } + if (cfg->keyboard_file) { + free(cfg->keyboard_file); + cfg->keyboard_file = NULL; + } + + m68k_clear_ranges(); + + printf("[CFG] Config file freed. Maybe.\n"); +} + struct emulator_config *load_config_file(char *filename) { FILE *in = fopen(filename, "rb"); if (in == NULL) { @@ -288,7 +371,7 @@ struct emulator_config *load_config_file(char *filename) { cfg->cpu_type = get_m68k_cpu_type(parse_line + str_pos); break; case CONFITEM_MAP: { - unsigned int maptype = 0, mapsize = 0, mapaddr = 0; + unsigned int maptype = 0, mapsize = 0, mapaddr = 0, autodump = 0; unsigned int mirraddr = ((unsigned int)-1); char mapfile[128], mapid[128]; memset(mapfile, 0x00, 128); @@ -333,12 +416,16 @@ struct emulator_config *load_config_file(char *filename) { get_next_string(parse_line, cur_cmd, &str_pos, ' '); mirraddr = get_int(cur_cmd); break; + case MAPCMD_AUTODUMP_FILE: + case MAPCMD_AUTODUMP_MEM: + autodump = get_map_cmd(cur_cmd); + break; default: printf("[CFG] Unknown/unhandled map argument %s on line %d.\n", cur_cmd, cur_line); break; } } - add_mapping(cfg, maptype, mapaddr, mapsize, mirraddr, mapfile, mapid); + add_mapping(cfg, maptype, mapaddr, mapsize, mirraddr, mapfile, mapid, autodump); break; } @@ -451,9 +538,24 @@ int get_mapped_item_by_address(struct emulator_config *cfg, uint32_t address) { for (int i = 0; i < MAX_NUM_MAPPED_ITEMS; i++) { if (cfg->map_type[i] == MAPTYPE_NONE || !cfg->map_data[i]) continue; - if (address >= cfg->map_offset[i] && address < cfg->map_high[i]) - return i; + else if (address >= cfg->map_offset[i] && address < cfg->map_high[i]) { + if (cfg->map_type[i] == MAPTYPE_RAM || cfg->map_type[i] == MAPTYPE_RAM_NOALLOC || cfg->map_type[i] == MAPTYPE_ROM) + return i; + } } return -1; } + +uint8_t *get_mapped_data_pointer_by_address(struct emulator_config *cfg, uint32_t address) { + for (int i = 0; i < MAX_NUM_MAPPED_ITEMS; i++) { + if (cfg->map_type[i] == MAPTYPE_NONE || !cfg->map_data[i]) + continue; + else if (address >= cfg->map_offset[i] && address < cfg->map_high[i]) { + if (cfg->map_type[i] == MAPTYPE_RAM || cfg->map_type[i] == MAPTYPE_RAM_NOALLOC || cfg->map_type[i] == MAPTYPE_ROM) + return cfg->map_data[i] + (address - cfg->map_offset[i]); + } + } + + return NULL; +} diff --git a/config_file/config_file.h b/config_file/config_file.h index 7ffacf9..841f802 100644 --- a/config_file/config_file.h +++ b/config_file/config_file.h @@ -17,6 +17,7 @@ typedef enum { MAPTYPE_ROM, MAPTYPE_RAM, MAPTYPE_REGISTER, + MAPTYPE_RAM_NOALLOC, MAPTYPE_NUM, } map_types; @@ -29,6 +30,8 @@ typedef enum { MAPCMD_FILENAME, MAPCMD_OVL_REMAP, MAPCMD_MAP_ID, + MAPCMD_AUTODUMP_FILE, + MAPCMD_AUTODUMP_MEM, MAPCMD_NUM, } map_cmds; @@ -93,13 +96,20 @@ struct platform_config { void (*setvar)(struct emulator_config *cfg, char *var, char *val); }; +#ifdef __cplusplus +extern "C" int get_mapped_item_by_address(struct emulator_config *cfg, uint32_t address); +#else unsigned int get_m68k_cpu_type(char *name); struct emulator_config *load_config_file(char *filename); +void free_config_file(struct emulator_config *cfg); int handle_mapped_read(struct emulator_config *cfg, unsigned int addr, unsigned int *val, unsigned char type); int handle_mapped_write(struct emulator_config *cfg, unsigned int addr, unsigned int value, unsigned char type); int get_named_mapped_item(struct emulator_config *cfg, char *name); int get_mapped_item_by_address(struct emulator_config *cfg, uint32_t address); +uint8_t *get_mapped_data_pointer_by_address(struct emulator_config *cfg, uint32_t address); +void add_mapping(struct emulator_config *cfg, unsigned int type, unsigned int addr, unsigned int size, int mirr_addr, char *filename, char *map_id, unsigned int autodump); unsigned int get_int(char *str); +#endif #endif /* _CONFIG_FILE_H */ diff --git a/config_file/rominfo.c b/config_file/rominfo.c new file mode 100644 index 0000000..75b4c6c --- /dev/null +++ b/config_file/rominfo.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include + +#include "rominfo.h" + +static void getDiagRom(uint8_t *address, struct romInfo *info) +{ + uint8_t *ptr = address + 0xC8; + uint8_t data = *ptr; + char *endptr = NULL; + if (data != 0x56) // V + { + return; + } + ptr++; + info->major = strtoul((const char*)ptr, &endptr, 10); + if (!endptr) + { + return; + } + data = *endptr; + if (data != '.') + { + info->minor = 0; + return; + } + endptr++; + info->minor = strtoul(endptr, &endptr, 10); + if (!endptr) + { + return; + } + info->isDiagRom = true; + data = *endptr; + if (data != '.') + { + info->extra = 0; + return; + } + endptr++; + info->extra = strtoul(endptr, NULL, 10); +} + +static void swapRom(uint8_t *address, size_t length) +{ + uint8_t *ptr = address; + for (size_t pos = 0; pos < length; pos = pos + 2) + { + uint8_t low = ptr[pos]; + uint8_t high = ptr[pos+1]; + ptr[pos] = high; + ptr[pos+1] = low; + } +} + +static void getRomInfo(uint8_t *address, size_t length, struct romInfo *info) +{ + uint8_t *ptr = address; + uint8_t data = *ptr; + info->isDiagRom = false; + + if ((ptr[2] == 0xF9) && (ptr[3] == 0x4E)) + { + // ROM byte swapped! + printf("[CFG] Byte swapped ROM found, swapping back\n"); + swapRom(address, length); + data = *ptr; + } + + if (data != 0x11) + { + info->id = ROM_TYPE_UNKNOWN; + + return; + } + ptr++; + data = *ptr; + switch (data) + { + case 0x11: + info->id = ROM_TYPE_256; + break; + case 0x14: + case 0x16: // kick40063.A600 + info->id = ROM_TYPE_512; + break; + default: + info->id = ROM_TYPE_UNKNOWN; + return; + break; + } + ptr++; + data = *ptr; + if (data != 0x4E) // 256K byte swapped + { + return; + } + // This is wrong endian for us + uint16_t ver_read; + memcpy(&ver_read, address+12, 2); + info->major = (ver_read >> 8) | (ver_read << 8); + memcpy(&ver_read, address+14, 2); + info->minor = (ver_read >> 8) | (ver_read << 8); + // We hit part of a memory ptr for DiagROM, it will be > 200 + if (info->major > 100) + { + getDiagRom(address, info); + } + return; +} + +void displayRomInfo(uint8_t *address, size_t length) +{ + struct romInfo info = {0}; + const char *kversion; + const char *size; + + getRomInfo(address, length, &info); + + if ((!info.isDiagRom) && (info.id != ROM_TYPE_UNKNOWN)) + { + switch(info.major) + { + case 27: + kversion = "Kickstart 0.7"; + break; + case 30: + kversion = "Kickstart 1.0"; + break; + case 31: + kversion = "Kickstart 1.1"; + break; + case 33: + kversion = "Kickstart 1.2"; + break; + case 34: + kversion = "Kickstart 1.3"; + break; + case 36: + { + // 36.002 and 36.015 were 1.4 + // 36.028, 36.067, 36.141 and 36.143 were 2.0 + // 36.207 was 2.02 + if (info.minor < 20) + { + kversion = "Kickstart 1.4"; + } + else if (info.minor < 200) + { + kversion = "Kickstart 2.0"; + } + else + { + kversion = "Kickstart 2.02"; + } + break; + } + case 37: + { + // 37.074 and 37.092 were 2.03 + // 37.175 was 2.04 + // 37.210, 37.299, 37.300 and 37.350 were 2.05 + if (info.minor < 100) + { + kversion = "Kickstart 2.03"; + } + else if (info.minor < 200) + { + kversion = "Kickstart 2.04"; + } + else + { + kversion = "Kickstart 2.05"; + } + break; + } + break; + case 39: + kversion = "Kickstart 3.0"; + break; + case 40: + kversion = "Kickstart 3.1"; + break; + case 45: + kversion = "Kickstart 3.x"; + break; + case 46: + kversion = "Kickstart 3.1.4"; + break; + case 47: + kversion = "Kickstart 3.2"; + break; + default: + kversion = "Unknown"; + break; + } + } + switch (info.id) + { + case ROM_TYPE_256: + size = "256KB"; + break; + case ROM_TYPE_512: + size = "512KB"; + break; + default: + size = ""; + break; + } + + if (info.isDiagRom) + { + printf("[CFG] ROM identified: DiagRom V%hu.%hu.%hu %s\n", info.major, info.minor, info.extra, size); + } + else if (info.id == ROM_TYPE_UNKNOWN) + { + printf("[CFG] ROM cannot be identified\n"); + } + else + { + printf("[CFG] ROM identified: %s (%hu.%hu) %s\n", kversion, info.major, info.minor, size); + } +} diff --git a/config_file/rominfo.h b/config_file/rominfo.h new file mode 100644 index 0000000..e7b304c --- /dev/null +++ b/config_file/rominfo.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +#ifndef __ROMINFO__ +#define __ROMINFO__ + +#include +#include + +enum romType { + ROM_TYPE_UNKNOWN, + ROM_TYPE_256, + ROM_TYPE_512, +}; + +struct romInfo { + enum romType id; + uint16_t major; + uint16_t minor; + uint16_t extra; + bool isDiagRom; +}; + +enum romErrCode { + ERR_NO_ERR, + ERR_NOT_ROM, + ERR_ROM_UNKNOWN +}; + +void displayRomInfo(uint8_t *address, size_t length); +#endif /* __ROMINFO */ diff --git a/data/a314-shared/README.md b/data/a314-shared/README.md new file mode 100644 index 0000000..c44ca2f --- /dev/null +++ b/data/a314-shared/README.md @@ -0,0 +1,3 @@ +# a314-shared Directory + +This is the default directory for files shared between the Pi and the A314 emulation's a314fs. diff --git a/data/fs/README.md b/data/fs/README.md new file mode 100644 index 0000000..b6dcdd5 --- /dev/null +++ b/data/fs/README.md @@ -0,0 +1,7 @@ +# data/fs directory + +This directory is used as storage for file systems loaded from hard drive images. +File systems can be loaded from here in case they're placed here under the correct file name if they're not embeded in the RDB on a hard drive image. + +"Correct file name" would be for instance `DOS.1` for `DOS/1` or `PFS.3` for `PFS/3`. +Unfortunately this can't yet be used to detect a difference between different revisions of for instance the PFS3AIO file system drivers. diff --git a/default.cfg b/default.cfg index 76d49e0..2716c3e 100644 --- a/default.cfg +++ b/default.cfg @@ -1,15 +1,20 @@ # Sets CPU type. Valid types are (probably) 68000, 68010, 68020, 68EC020, 68030, 68EC030, 68040, 68EC040, 68LC040 and some STTTT thing. cpu 68020 # Map 512KB kickstart ROM to default offset. -map type=rom address=0xF80000 size=0x80000 file=kick.rom ovl=0 +map type=rom address=0xF80000 size=0x80000 file=kick.rom ovl=0 id=kickstart +# Comment out the line above and uncomment the line below to automatically copy the ROM contents to Pi RAM if the file isn't found +#map type=rom address=0xF80000 size=0x80000 file=kick.rom ovl=0 id=kickstart autodump_mem + # Want to map an extended ROM, such as CDTV or CD32? -#map type=rom address=0xF00000 size=0x80000 file=cdtv.rom +#map type=rom address=0xF00000 size=0x80000 file=cdtv.rom id=extended # Map 128MB of Fast RAM at 0x8000000, also known as 32-bit Fast RAM or CPU local Fast RAM. # Only supported properly on select Kickstarts, such as 3.1+ for Amiga 1200, 3000 and 4000. #map type=ram address=0x08000000 size=128M id=cpu_slot_ram # Map 128MB of Z3 Fast. Note that the address here is not actually used, as it gets auto-assigned by Kickstart itself. # Enabling Z3 fast requires at least Kickstart 2.0. +# If for some reason you need more than 128MB, you can change the size=128M to a valid supported Z3 PIC size, +# like 256M, 512M, 1024M, or add additional map lines. map type=ram address=0x10000000 size=128M id=z3_autoconf_fast # Max 8MB of Z2 Fast can be mapped due to addressing space limitations, but for instance 2+4MB can be chained to leave 2MB for something else. #map type=ram address=0x200000 size=8M id=z2_autoconf_fast @@ -23,7 +28,7 @@ map type=ram address=0x10000000 size=128M id=z3_autoconf_fast # NOTE: Gayle emulation is currently non-functional. Do not enable this. #map type=register address=0xD80000 size=0x40000 # Map RTC as a register range. -map type=register address=0xDC0000 size=0x30000 +map type=register address=0xDC0000 size=0x10000 # Number of instructions to run every main loop. loopcycles 300 # Set the platform to Amiga to enable all the registers and stuff. @@ -32,6 +37,7 @@ platform amiga #setvar enable_rtc_emulation 0 # Uncomment to enable RTG #setvar rtg + # Uncomment to enable CDTV mode (not working, requires Kickstart 1.3+CDTV extended ROM) #setvar cdtv # Uncomment this line to enable the PiSCSI interface @@ -39,9 +45,16 @@ platform amiga # Use setvar piscsi0 through piscsi6 to add up to seven mapped drives to the interface. #setvar piscsi0 PI0.hdf #setvar piscsi1 PI1.hdf +# A special disk that includes PiStorm drivers and utilities, comment out if not needed +setvar piscsi6 platforms/amiga/pistorm.hdf # Uncomment this line to enable the (currently non-working) Pi-Net interface. #setvar pi-net +# Uncomment and edit to set a custom config filename for the A314 emulation +#setvar a314_conf ./a314/files_pi/a314d.conf +# Uncomment to enable A314 emulation +#setvar a314 + # Forward keyboard events to host system, defaults to off unless toggle key is pressed, toggled off using F12. # Syntax: keyboard [grab key] [grab|nograb] [autoconnect|noautoconnect] # "grab" steals the keyboard from the Pi so Amiga/etc. input is not sent to the Pi diff --git a/emulator.c b/emulator.c index 1be7bcd..7e48e0b 100644 --- a/emulator.c +++ b/emulator.c @@ -14,6 +14,8 @@ #include "platforms/amiga/piscsi/piscsi-enums.h" #include "platforms/amiga/net/pi-net.h" #include "platforms/amiga/net/pi-net-enums.h" +#include "platforms/amiga/pistorm-dev/pistorm-dev.h" +#include "platforms/amiga/pistorm-dev/pistorm-dev-enums.h" #include "gpio/ps_protocol.h" #include @@ -60,13 +62,13 @@ extern uint8_t gayle_emulation_enabled; extern uint8_t gayle_a4k_int; extern volatile unsigned int *gpio; extern volatile uint16_t srdata; -extern uint8_t realtime_graphics_debug; +extern uint8_t realtime_graphics_debug, emulator_exiting; extern uint8_t rtg_on; uint8_t realtime_disassembly, int2_enabled = 0; uint32_t do_disasm = 0, old_level; uint32_t last_irq = 8, last_last_irq = 8; -uint8_t end_signal = 0; +uint8_t end_signal = 0, load_new_config = 0; char disasm_buf[4096]; @@ -126,11 +128,13 @@ void *ipl_task(void *args) { value = *(gpio + 13); if (!(value & (1 << PIN_IPL_ZERO))) { - irq = 1; old_irq = irq_delay; //NOP - M68K_END_TIMESLICE; - NOP + if (!irq) { + M68K_END_TIMESLICE; + NOP + irq = 1; + } //usleep(0); } else { @@ -262,6 +266,11 @@ cpu_loop: mouse_extra = 0x00; } + if (load_new_config) { + printf("[CPU] Loading new config file.\n"); + goto stop_cpu_emulation; + } + if (end_signal) goto stop_cpu_emulation; @@ -426,6 +435,11 @@ void sigint_handler(int sig_num) { cfg->platform->shutdown(cfg); } + while (!emulator_exiting) { + emulator_exiting = 1; + usleep(0); + } + printf("IRQs triggered: %lld\n", trig_irq); printf("IRQs serviced: %lld\n", serv_irq); @@ -434,6 +448,9 @@ void sigint_handler(int sig_num) { int main(int argc, char *argv[]) { int g; + + ps_setup_protocol(); + //const struct sched_param priority = {99}; // Some command line switch stuffles @@ -451,7 +468,14 @@ int main(int argc, char *argv[]) { printf("%s switch found, but no config filename specified.\n", argv[g]); } else { g++; - cfg = load_config_file(argv[g]); + FILE *chk = fopen(argv[g], "rb"); + if (chk == NULL) { + printf("Config file %s does not exist, please check that you've specified the path correctly.\n", argv[g]); + } else { + fclose(chk); + load_new_config = 1; + set_pistorm_devcfg_filename(argv[g]); + } } } else if (strcmp(argv[g], "--keyboard-file") == 0 || strcmp(argv[g], "--kbfile") == 0) { @@ -464,6 +488,33 @@ int main(int argc, char *argv[]) { } } +switch_config: + srand(clock()); + + ps_reset_state_machine(); + ps_pulse_reset(); + usleep(1500); + + if (load_new_config != 0) { + uint8_t config_action = load_new_config - 1; + load_new_config = 0; + if (cfg) { + free_config_file(cfg); + free(cfg); + cfg = NULL; + } + + switch(config_action) { + case PICFG_LOAD: + case PICFG_RELOAD: + cfg = load_config_file(get_pistorm_devcfg_filename()); + break; + case PICFG_DEFAULT: + cfg = load_config_file("default.cfg"); + break; + } + } + if (!cfg) { printf("No config file specified. Trying to load default.cfg...\n"); cfg = load_config_file("default.cfg"); @@ -529,56 +580,26 @@ int main(int argc, char *argv[]) { InitGayle(); signal(SIGINT, sigint_handler); - /*setup_io(); - - //goto skip_everything; - - // Enable 200MHz CLK output on GPIO4, adjust divider and pll source depending - // on pi model - printf("Enable 200MHz GPCLK0 on GPIO4\n"); - gpio_enable_200mhz(); - - // reset cpld statemachine first - - write_reg(0x01); - usleep(100); - usleep(1500); - write_reg(0x00); - usleep(100); - - // reset amiga and statemachine - skip_everything:; - usleep(1500); - - m68k_init(); - printf("Setting CPU type to %d.\n", cpu_type); - m68k_set_cpu_type(cpu_type); - cpu_pulse_reset(); - - if (maprom == 1) { - m68k_set_reg(M68K_REG_PC, 0xF80002); - } else { - m68k_set_reg(M68K_REG_PC, 0x0); - }*/ - ps_setup_protocol(); ps_reset_state_machine(); ps_pulse_reset(); - usleep(1500); + m68k_init(); printf("Setting CPU type to %d.\n", cpu_type); m68k_set_cpu_type(cpu_type); cpu_pulse_reset(); - pthread_t ipl_tid, cpu_tid, kbd_tid; + pthread_t ipl_tid = 0, cpu_tid, kbd_tid; int err; - err = pthread_create(&ipl_tid, NULL, &ipl_task, NULL); - if (err != 0) - printf("[ERROR] Cannot create IPL thread: [%s]", strerror(err)); - else { - pthread_setname_np(ipl_tid, "pistorm: ipl"); - printf("IPL thread created successfully\n"); + if (ipl_tid == 0) { + err = pthread_create(&ipl_tid, NULL, &ipl_task, NULL); + if (err != 0) + printf("[ERROR] Cannot create IPL thread: [%s]", strerror(err)); + else { + pthread_setname_np(ipl_tid, "pistorm: ipl"); + printf("IPL thread created successfully\n"); + } } // create keyboard task @@ -601,30 +622,39 @@ int main(int argc, char *argv[]) { // wait for cpu task to end before closing up and finishing pthread_join(cpu_tid, NULL); - printf("[MAIN] All threads appear to have concluded; ending process\n"); + + while (!emulator_exiting) { + emulator_exiting = 1; + usleep(0); + } + + if (load_new_config == 0) + printf("[MAIN] All threads appear to have concluded; ending process\n"); if (mouse_fd != -1) close(mouse_fd); if (mem_fd) close(mem_fd); + if (load_new_config != 0) + goto switch_config; + + if (cfg->platform->shutdown) { + cfg->platform->shutdown(cfg); + } + return 0; } void cpu_pulse_reset(void) { ps_pulse_reset(); - //write_reg(0x00); - // printf("Status Reg%x\n",read_reg()); - //usleep(100000); - //write_reg(0x02); - // printf("Status Reg%x\n",read_reg()); if (cfg->platform->handle_reset) cfg->platform->handle_reset(cfg); //m68k_write_memory_16(INTENA, 0x7FFF); ovl = 1; - m68k_write_memory_8(0xbfe201, 0x0001); // AMIGA OVL - m68k_write_memory_8(0xbfe001, 0x0001); // AMIGA OVL high (ROM@0x0) + //m68k_write_memory_8(0xbfe201, 0x0001); // AMIGA OVL + //m68k_write_memory_8(0xbfe001, 0x0001); // AMIGA OVL high (ROM@0x0) m68k_pulse_reset(); } @@ -636,141 +666,140 @@ int cpu_irq_ack(int level) { static unsigned int target = 0; static uint8_t send_keypress = 0; +static uint32_t platform_res, rres; uint8_t cdtv_dmac_reg_idx_read(); void cdtv_dmac_reg_idx_write(uint8_t value); uint32_t cdtv_dmac_read(uint32_t address, uint8_t type); void cdtv_dmac_write(uint32_t address, uint32_t value, uint8_t type); -#define PLATFORM_CHECK_READ(a) \ - if (address >= cfg->custom_low && address < cfg->custom_high) { \ - unsigned int target = 0; \ - switch(cfg->platform->id) { \ - case PLATFORM_AMIGA: { \ - if (address >= PISCSI_OFFSET && address < PISCSI_UPPER) { \ - return handle_piscsi_read(address, a); \ - } \ - if (address >= PINET_OFFSET && address < PINET_UPPER) { \ - return handle_pinet_read(address, a); \ - } \ - if (address >= PIGFX_RTG_BASE && address < PIGFX_UPPER) { \ - return rtg_read((address & 0x0FFFFFFF), a); \ - } \ - if (custom_read_amiga(cfg, address, &target, a) != -1) { \ - return target; \ - } \ - break; \ - } \ - default: \ - break; \ - } \ - } \ - if (ovl || (address >= cfg->mapped_low && address < cfg->mapped_high)) { \ - if (handle_mapped_read(cfg, address, &target, a) != -1) \ - return target; \ - } - -unsigned int m68k_read_memory_8(unsigned int address) { - PLATFORM_CHECK_READ(OP_TYPE_BYTE); - - /*if (address >= 0xE90000 && address < 0xF00000) { - printf("BYTE read from DMAC @%.8X:", address); - uint32_t v = cdtv_dmac_read(address & 0xFFFF, OP_TYPE_BYTE); - printf("%.2X\n", v); - M68K_END_TIMESLICE; - cpu_emulation_running = 0; - return v; - }*/ - - /*if (m68k_get_reg(NULL, M68K_REG_PC) >= 0x080032F0 && m68k_get_reg(NULL, M68K_REG_PC) <= 0x080032F0 + 0x4000) { - stop_cpu_emulation(1); - }*/ - - - if (address & 0xFF000000) - return 0; - - unsigned char result = (unsigned int)read8((uint32_t)address); - - if (mouse_hook_enabled) { - if (address == CIAAPRA) { - if (mouse_buttons & 0x01) { - //mouse_buttons -= 1; - return (unsigned int)(result ^ 0x40); - } - - return (unsigned int)result; - } +static inline uint32_t ps_read(uint8_t type, uint32_t addr) { + switch (type) { + case OP_TYPE_BYTE: + return ps_read_8(addr); + case OP_TYPE_WORD: + return ps_read_16(addr); + case OP_TYPE_LONGWORD: + return ps_read_32(addr); } + // This shouldn't actually happen. + return 0; +} - if (kb_hook_enabled) { - if (address == CIAAICR) { - if (get_num_kb_queued() && (!send_keypress || send_keypress == 1)) { - result |= 0x08; - if (!send_keypress) - send_keypress = 1; +static inline int32_t platform_read_check(uint8_t type, uint32_t addr, uint32_t *res) { + switch (addr) { + case CIAAPRA: + if (mouse_hook_enabled && (mouse_buttons & 0x01)) { + rres = (uint32_t)ps_read(type, addr); + *res = (rres ^ 0x40); + return 1; } - if (send_keypress == 2) { - //result |= 0x02; - send_keypress = 0; + return 0; + break; + case CIAAICR: + if (kb_hook_enabled) { + rres = (uint32_t)ps_read(type, addr); + if (get_num_kb_queued() && (!send_keypress || send_keypress == 1)) { + rres |= 0x08; + if (!send_keypress) + send_keypress = 1; + } + if (send_keypress == 2) { + send_keypress = 0; + } + *res = rres; + return 1; } - return result; - } - if (address == CIAADAT) { - //if (send_keypress) { + return 0; + break; + case CIAADAT: + if (kb_hook_enabled) { + rres = (uint32_t)ps_read(type, addr); uint8_t c = 0, t = 0; pop_queued_key(&c, &t); t ^= 0x01; - result = ((c << 1) | t) ^ 0xFF; + rres = ((c << 1) | t) ^ 0xFF; send_keypress = 2; - //M68K_SET_IRQ(0); - //} - return result; + *res = rres; + return 1; + } + return 0; + break; + case JOY0DAT: + if (mouse_hook_enabled) { + unsigned short result = (mouse_dy << 8) | (mouse_dx); + *res = (unsigned int)result; + return 1; + } + return 0; + break; + case POTGOR: + if (mouse_hook_enabled) { + unsigned short result = (unsigned short)ps_read(type, addr); + // bit 1 rmb, bit 2 mmb + if (mouse_buttons & 0x06) { + *res = (unsigned int)((result ^ ((mouse_buttons & 0x02) << 9)) // move rmb to bit 10 + & (result ^ ((mouse_buttons & 0x04) << 6))); // move mmb to bit 8 + return 1; + } + *res = (unsigned int)(result & 0xfffd); + return 1; + } + return 0; + break; + default: + break; + } + + switch (cfg->platform->id) { + case PLATFORM_AMIGA: + if (addr >= cfg->custom_low && addr < cfg->custom_high) { + if (addr >= PISCSI_OFFSET && addr < PISCSI_UPPER) { + *res = handle_piscsi_read(addr, type); + return 1; + } + if (addr >= PINET_OFFSET && addr < PINET_UPPER) { + *res = handle_pinet_read(addr, type); + return 1; + } + if (addr >= PIGFX_RTG_BASE && addr < PIGFX_UPPER) { + *res = rtg_read((addr & 0x0FFFFFFF), type); + return 1; + } + if (custom_read_amiga(cfg, addr, &target, type) != -1) { + *res = target; + return 1; + } + } + break; + default: + break; + } + + if (ovl || (addr >= cfg->mapped_low && addr < cfg->mapped_high)) { + if (handle_mapped_read(cfg, addr, &target, type) != -1) { + *res = target; + return 1; } } - return result; + return 0; } -unsigned int m68k_read_memory_16(unsigned int address) { - PLATFORM_CHECK_READ(OP_TYPE_WORD); +unsigned int m68k_read_memory_8(unsigned int address) { + if (platform_read_check(OP_TYPE_BYTE, address, &platform_res)) { + return platform_res; + } - /*if (m68k_get_reg(NULL, M68K_REG_PC) >= 0x080032F0 && m68k_get_reg(NULL, M68K_REG_PC) <= 0x080032F0 + 0x4000) { - stop_cpu_emulation(1); - }*/ + if (address & 0xFF000000) + return 0; - /*if (address >= 0xE90000 && address < 0xF00000) { - printf("WORD read from DMAC @%.8X:", address); - uint32_t v = cdtv_dmac_read(address & 0xFFFF, OP_TYPE_WORD); - printf("%.2X\n", v); - M68K_END_TIMESLICE; - cpu_emulation_running = 0; - return v; - }*/ + return (unsigned int)read8((uint32_t)address); +} - if (mouse_hook_enabled) { - if (address == JOY0DAT) { - // Forward mouse valueses to Amyga. - unsigned short result = (mouse_dy << 8) | (mouse_dx); - return (unsigned int)result; - } - /*if (address == CIAAPRA) { - unsigned short result = (unsigned int)read16((uint32_t)address); - if (mouse_buttons & 0x01) { - return (unsigned int)(result | 0x40); - } - else - return (unsigned int)result; - }*/ - if (address == POTGOR) { - unsigned short result = (unsigned int)read16((uint32_t)address); - // bit 1 rmb, bit 2 mmb - if (mouse_buttons & 0x06) { - return (unsigned int)((result ^ ((mouse_buttons & 0x02) << 9)) // move rmb to bit 10 - & (result ^ ((mouse_buttons & 0x04) << 6))); // move mmb to bit 8 - } - return (unsigned int)(result & 0xfffd); - } +unsigned int m68k_read_memory_16(unsigned int address) { + if (platform_read_check(OP_TYPE_WORD, address, &platform_res)) { + return platform_res; } if (address & 0xFF000000) @@ -783,20 +812,9 @@ unsigned int m68k_read_memory_16(unsigned int address) { } unsigned int m68k_read_memory_32(unsigned int address) { - PLATFORM_CHECK_READ(OP_TYPE_LONGWORD); - - /*if (m68k_get_reg(NULL, M68K_REG_PC) >= 0x080032F0 && m68k_get_reg(NULL, M68K_REG_PC) <= 0x080032F0 + 0x4000) { - stop_cpu_emulation(1); - }*/ - - /*if (address >= 0xE90000 && address < 0xF00000) { - printf("LONGWORD read from DMAC @%.8X:", address); - uint32_t v = cdtv_dmac_read(address & 0xFFFF, OP_TYPE_LONGWORD); - printf("%.2X\n", v); - M68K_END_TIMESLICE; - cpu_emulation_running = 0; - return v; - }*/ + if (platform_read_check(OP_TYPE_LONGWORD, address, &platform_res)) { + return platform_res; + } if (address & 0xFF000000) return 0; @@ -812,51 +830,76 @@ unsigned int m68k_read_memory_32(unsigned int address) { return (a << 16) | b; } -#define PLATFORM_CHECK_WRITE(a) \ - if (address >= cfg->custom_low && address < cfg->custom_high) { \ - switch(cfg->platform->id) { \ - case PLATFORM_AMIGA: { \ - if (address >= PISCSI_OFFSET && address < PISCSI_UPPER) { \ - handle_piscsi_write(address, value, a); \ - } \ - if (address >= PINET_OFFSET && address < PINET_UPPER) { \ - handle_pinet_write(address, value, a); \ - } \ - if (address >= PIGFX_RTG_BASE && address < PIGFX_UPPER) { \ - rtg_write((address & 0x0FFFFFFF), value, a); \ - return; \ - } \ - if (custom_write_amiga(cfg, address, value, a) != -1) { \ - return; \ - } \ - break; \ - } \ - default: \ - break; \ - } \ - } \ - if (address >= cfg->mapped_low && address < cfg->mapped_high) { \ - if (handle_mapped_write(cfg, address, value, a) != -1) \ - return; \ +static inline int32_t platform_write_check(uint8_t type, uint32_t addr, uint32_t val) { + switch (addr) { + case CIAAPRA: + if (ovl != (val & (1 << 0))) { + ovl = (val & (1 << 0)); + printf("OVL:%x\n", ovl); + } + return 0; + break; + case SERDAT: { + char *serdat = (char *)&val; + // SERDAT word. see amiga dev docs appendix a; upper byte is control codes, and bit 0 is always 1. + // ignore this upper byte as it's not viewable data, only display lower byte. + printf("%c", serdat[0]); + return 0; + break; + } + case INTENA: + // This code is kind of strange and should probably be reworked/revoked. + if (!(val & 0x8000)) { + if (val & 0x04) { + int2_enabled = 0; + } + } + else if (val & 0x04) { + int2_enabled = 1; + } + return 0; + break; + default: + break; + } + + switch (cfg->platform->id) { + case PLATFORM_AMIGA: + if (addr >= cfg->custom_low && addr < cfg->custom_high) { + if (addr >= PISCSI_OFFSET && addr < PISCSI_UPPER) { + handle_piscsi_write(addr, val, type); + return 1; + } + if (addr >= PINET_OFFSET && addr < PINET_UPPER) { + handle_pinet_write(addr, val, type); + return 1; + } + if (addr >= PIGFX_RTG_BASE && addr < PIGFX_UPPER) { + rtg_write((addr & 0x0FFFFFFF), val, type); + return 1; + } + if (custom_write_amiga(cfg, addr, val, type) != -1) { + return 1; + } + } + break; + default: + break; + } + + if (ovl || (addr >= cfg->mapped_low && addr < cfg->mapped_high)) { + if (handle_mapped_write(cfg, addr, val, type) != -1) { + return 1; + } } -void m68k_write_memory_8(unsigned int address, unsigned int value) { - PLATFORM_CHECK_WRITE(OP_TYPE_BYTE); + return 0; +} - /*if (address >= 0xE90000 && address < 0xF00000) { - printf("BYTE write to DMAC @%.8X: %.2X\n", address, value); - cdtv_dmac_write(address & 0xFFFF, value, OP_TYPE_BYTE); - M68K_END_TIMESLICE; - cpu_emulation_running = 0; - return; - }*/ - if (address == 0xbfe001) { - if (ovl != (value & (1 << 0))) { - ovl = (value & (1 << 0)); - printf("OVL:%x\n", ovl); - } - } +void m68k_write_memory_8(unsigned int address, unsigned int value) { + if (platform_write_check(OP_TYPE_BYTE, address, value)) + return; if (address & 0xFF000000) return; @@ -866,59 +909,35 @@ void m68k_write_memory_8(unsigned int address, unsigned int value) { } void m68k_write_memory_16(unsigned int address, unsigned int value) { - PLATFORM_CHECK_WRITE(OP_TYPE_WORD); - - /*if (address >= 0xE90000 && address < 0xF00000) { - printf("WORD write to DMAC @%.8X: %.4X\n", address, value); - cdtv_dmac_write(address & 0xFFFF, value, OP_TYPE_WORD); - M68K_END_TIMESLICE; - cpu_emulation_running = 0; + if (platform_write_check(OP_TYPE_WORD, address, value)) return; - }*/ - - if (address == 0xDFF030) { - char *serdat = (char *)&value; - // SERDAT word. see amiga dev docs appendix a; upper byte is control codes, and bit 0 is always 1. - // ignore this upper byte as it's not viewable data, only display lower byte. - printf("%c", serdat[0]); - } - if (address == 0xDFF09A) { - if (!(value & 0x8000)) { - if (value & 0x04) { - int2_enabled = 0; - } - } - else if (value & 0x04) { - int2_enabled = 1; - } - } if (address & 0xFF000000) return; - if (address & 0x01) - printf("Unaligned WORD write!\n"); + if (address & 0x01) { + write8(value & 0xFF, address); + write8((value >> 8) & 0xFF, address + 1); + return; + } write16((uint32_t)address, value); return; } void m68k_write_memory_32(unsigned int address, unsigned int value) { - PLATFORM_CHECK_WRITE(OP_TYPE_LONGWORD); - - /*if (address >= 0xE90000 && address < 0xF00000) { - printf("LONGWORD write to DMAC @%.8X: %.8X\n", address, value); - cdtv_dmac_write(address & 0xFFFF, value, OP_TYPE_LONGWORD); - M68K_END_TIMESLICE; - cpu_emulation_running = 0; + if (platform_write_check(OP_TYPE_LONGWORD, address, value)) return; - }*/ if (address & 0xFF000000) return; - if (address & 0x01) - printf("Unaligned LONGWORD write!\n"); + if (address & 0x01) { + write8(value & 0xFF, address); + write16(htobe16(((value >> 8) & 0xFFFF)), address + 1); + write8((value >> 24), address + 3); + return; + } write16(address, value >> 16); write16(address + 2, value); diff --git a/i2c_updater/.gitignore b/i2c_updater/.gitignore new file mode 100644 index 0000000..0f6eaf8 --- /dev/null +++ b/i2c_updater/.gitignore @@ -0,0 +1,3 @@ +*.bit +build +.vscode diff --git a/i2c_updater/pi-i2c/CMakeLists.txt b/i2c_updater/pi-i2c/CMakeLists.txt new file mode 100644 index 0000000..4eb7e42 --- /dev/null +++ b/i2c_updater/pi-i2c/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.15.2) +project(pistorm_i2c_updater C CXX) + +file(GLOB i2c_updater_sources src/*.cpp src/*.hpp) +add_executable(i2c_updater ${i2c_updater_sources}) +set_property(TARGET i2c_updater PROPERTY CXX_STANDARD 17) +target_compile_options(i2c_updater PRIVATE -Wall) diff --git a/i2c_updater/pi-i2c/src/bitstream.cpp b/i2c_updater/pi-i2c/src/bitstream.cpp new file mode 100644 index 0000000..c86e85f --- /dev/null +++ b/i2c_updater/pi-i2c/src/bitstream.cpp @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "bitstream.hpp" +#include +#include +#include "file.hpp" + +// Based on https://prjtrellis.readthedocs.io/en/latest/architecture/bitstream_format.html , +// Lattice Diamond programmer operation's I2C dump and "ECP5 and ECP5-5G sysCONFIG Usage Guide" + +#define SECTION_ASCII_COMMENTS_BEGIN 0xFF00 +#define SECTION_PREAMBLE_WORD_1 0xFFFF +#define SECTION_PREAMBLE_WORD_2 0xBDB3 + +struct tBitstreamCmd { + enum class tId: uint8_t { + LSC_PROG_CNTRL0 = 0x22, + LSC_RESET_CRC = 0x3B, + LSC_INIT_ADDRESS = 0x46, + ISC_PROGRAM_DONE = 0x5E, + LSC_PROG_INCR_RTI = 0x82, + LSC_PROG_SED_CRC = 0xA2, + LSC_EBR_WRITE = 0xB2, + ISC_PROGRAM_USERCODE = 0xC2, + ISC_PROGRAM_SECURITY = 0xCE, + LSC_VERIFY_ID = 0xE2, + LSC_EBR_ADDRESS = 0xF6, + ISC_NOOP = 0xFF, + }; + + tBitstreamCmd::tId m_eId; + std::array m_Operands; + std::vector m_vData; + + tBitstreamCmd(std::ifstream &FileIn); +}; + +tBitstream::tBitstream(const std::string &FilePath) +{ + std::ifstream FileIn; + FileIn.open(FilePath.c_str(), std::ifstream::in | std::ifstream::binary); + if(!FileIn.good()) { + throw std::runtime_error("Couldn't open file"); + } + + uint16_t uwSectionId; + nFile::readBigEndian(FileIn, uwSectionId); + if(uwSectionId == SECTION_ASCII_COMMENTS_BEGIN) { + // Read comments + std::string Comment; + char c; + while(!FileIn.eof()) { + nFile::readBigEndian(FileIn, c); + if(c == '\xFF') { + break; + } + if(c == '\0') { + m_vComments.push_back(Comment); + Comment.clear(); + } + else { + Comment += c; + } + } + + // Continue reading until we hit preamble + nFile::readBigEndian(FileIn, uwSectionId); + } + + if(uwSectionId != SECTION_PREAMBLE_WORD_1) { + // I want std::format so bad :( + std::stringstream ss; + ss << "Unexpected bitstream section: " << std::setfill('0') << + std::setw(4) << std::ios::hex << uwSectionId; + throw std::runtime_error(ss.str()); + } + + // Read final part of preamble + uint16_t uwPreambleEnd; + nFile::readBigEndian(FileIn, uwPreambleEnd); + if(uwPreambleEnd != SECTION_PREAMBLE_WORD_2) { + // I want std::format so bad :( + std::stringstream ss; + ss << "Unexpected preamble ending" << std::setfill('0') << + std::setw(4) << std::ios::hex << uwPreambleEnd; + throw std::runtime_error(ss.str()); + } + + while(!FileIn.eof()) { + // Read bitstream commands + tBitstreamCmd Cmd(FileIn); + if(FileIn.eof()) { + // Last read haven't been successfull - no more data + break; + } + + // Read extra data, if any + switch(Cmd.m_eId) { + case tBitstreamCmd::tId::ISC_NOOP: + case tBitstreamCmd::tId::LSC_RESET_CRC: + case tBitstreamCmd::tId::LSC_INIT_ADDRESS: + // No additional data + break; + case tBitstreamCmd::tId::LSC_VERIFY_ID: + nFile::readData(FileIn, m_DeviceId.data(), m_DeviceId.size()); + break; + case tBitstreamCmd::tId::LSC_PROG_CNTRL0: + nFile::readBigEndian(FileIn, m_ulCtlReg0); + break; + case tBitstreamCmd::tId::LSC_PROG_INCR_RTI: { + // Store program data + // bool isCrcVerify = Cmd.m_Operands[0] & 0x80; + // bool isCrcAtEnd = !(Cmd.m_Operands[0] & 0x40); + // bool isDummyBits = !(Cmd.m_Operands[0] & 0x20); + // bool isDummyBytes = !(Cmd.m_Operands[0] & 0x10); + auto DummyBytesCount = Cmd.m_Operands[0] & 0xF; + uint16_t uwRowCount = (Cmd.m_Operands[1] << 8) | Cmd.m_Operands[2]; + + for(auto RowIdx = 0; RowIdx < uwRowCount; ++RowIdx) { + // TODO: where to get the size of each data row? From comment? + std::vector vRow; + vRow.resize(26); + uint16_t uwCrc; + nFile::readData(FileIn, vRow.data(), vRow.size()); + nFile::readBigEndian(FileIn, uwCrc); + if(uwCrc == 0) { + // I want std::format so bad :( + std::stringstream ss; + ss << "Empty CRC near pos " << std::hex << FileIn.tellg(); + throw std::runtime_error(ss.str()); + } + m_vProgramData.push_back(vRow); + FileIn.seekg(DummyBytesCount, std::ios::cur); + } + } break; + case tBitstreamCmd::tId::ISC_PROGRAM_USERCODE: + // Read usercode, skip CRC + nFile::readData(FileIn, m_UserCode.data(), m_UserCode.size()); + FileIn.seekg(2, std::ios::cur); + break; + case tBitstreamCmd::tId::ISC_PROGRAM_DONE: + break; + default: { + // I want std::format so bad :( + std::stringstream ss; + ss << "Unhandled bitstream cmd near file pos 0x" << std::hex << + FileIn.tellg() << ": 0x" << std::setw(2) << std::setfill('0') << + int(Cmd.m_eId); + throw std::runtime_error(ss.str()); + } + } + } +} + +tBitstreamCmd::tBitstreamCmd(std::ifstream &FileIn) +{ + uint32_t ulCmdRaw; + nFile::readBigEndian(FileIn, ulCmdRaw); + m_eId = tBitstreamCmd::tId(ulCmdRaw >> 24); + m_Operands[0] = (ulCmdRaw >> 16) & 0xFF; + m_Operands[1] = (ulCmdRaw >> 8) & 0xFF; + m_Operands[2] = (ulCmdRaw >> 0) & 0xFF; +} diff --git a/i2c_updater/pi-i2c/src/bitstream.hpp b/i2c_updater/pi-i2c/src/bitstream.hpp new file mode 100644 index 0000000..e8c0b88 --- /dev/null +++ b/i2c_updater/pi-i2c/src/bitstream.hpp @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _BITSTREAM_HPP_ +#define _BITSTREAM_HPP_ + +#include +#include +#include +#include + +struct tBitstream { + tBitstream(const std::string &FilePath); + + std::array m_DeviceId; + uint32_t m_ulCtlReg0; + std::array m_UserCode; + std::vector m_vComments; + std::vector> m_vProgramData; +}; + +#endif // _BITSTREAM_HPP_ diff --git a/i2c_updater/pi-i2c/src/endian.hpp b/i2c_updater/pi-i2c/src/endian.hpp new file mode 100644 index 0000000..552912f --- /dev/null +++ b/i2c_updater/pi-i2c/src/endian.hpp @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _ENDIAN_HPP_ +#define _ENDIAN_HPP_ + +#include +// #include // we don't have GCC with C++20 on raspi :( + +namespace nEndian { + +template +constexpr t_tVal swapBytes(t_tVal Val) { + t_tVal Out = 0; + for(size_t i = 0; i < sizeof(Val); ++i) { + Out = (Out << 8) | (Val & 0xFF); + Val >>= 8; + } + return Out; +} + +template +constexpr t_tVal littleToNative(const t_tVal &Val) { + // if(std::endian::native == std::endian::big) { + // return swapBytes(Val); + // } + // else { + return Val; + // } +} + +template +constexpr auto nativeToLittle = littleToNative; + +template +constexpr t_tVal bigToNative(const t_tVal &Val) { + // if(std::endian::native == std::endian::big) { + // return Val; + // } + // else { + return swapBytes(Val); + // } +} + +template +constexpr auto nativeToBig = bigToNative; + + +} // namespace nEndian + +#endif // _ENDIAN_HPP_ diff --git a/i2c_updater/pi-i2c/src/file.hpp b/i2c_updater/pi-i2c/src/file.hpp new file mode 100644 index 0000000..5ee7b54 --- /dev/null +++ b/i2c_updater/pi-i2c/src/file.hpp @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _FILE_HPP_ +#define _FILE_HPP_ + +#include +#include "endian.hpp" + +namespace nFile { + +template +t_tStream readLittleEndian(t_tStream &File, t_tData &Data) +{ + File.read(reinterpret_cast(&Data), sizeof(Data)); + Data = nEndian::littleToNative(Data); +} + +template +void readBigEndian(t_tStream &File, t_tData &Data) +{ + File.read(reinterpret_cast(&Data), sizeof(Data)); + Data = nEndian::bigToNative(Data); +} + +template +void readData(t_tStream &File, t_tData *pData, size_t ElementCount) +{ + File.read(reinterpret_cast(pData), ElementCount * sizeof(*pData)); +} + +} // namespace nFile + +#endif // _FILE_HPP_ diff --git a/i2c_updater/pi-i2c/src/i2c.cpp b/i2c_updater/pi-i2c/src/i2c.cpp new file mode 100644 index 0000000..e80396d --- /dev/null +++ b/i2c_updater/pi-i2c/src/i2c.cpp @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "i2c.hpp" + +#include +#include +#include +#include +#include + +tI2c::tI2c(const std::string &Port) +{ + // Open the I2C bus file handle + m_I2cHandle = open(Port.c_str(), O_RDWR); + if(m_I2cHandle < 0) { + // TODO: check errno to see what went wrong + throw std::runtime_error("Can't open the i2c bus\n"); + } +} + +bool tI2c::write(uint8_t ubAddr, const std::vector &vData) { + if(ioctl(m_I2cHandle, I2C_SLAVE, ubAddr) < 0) { + // NOTE: check errno to see what went wrong + return false; + } + + auto BytesWritten = ::write(m_I2cHandle, vData.data(), vData.size()); + if(BytesWritten != ssize_t(vData.size())) { + return false; + } + + return true; +} + +bool tI2c::read(uint8_t ubAddr, uint8_t *pDest, uint32_t ulReadSize) { + if(ioctl(m_I2cHandle, I2C_SLAVE, ubAddr) < 0) { + // NOTE: check errno to see what went wrong + return false; + } + + auto BytesRead = ::read(m_I2cHandle, pDest, ulReadSize); + if(BytesRead != ssize_t(ulReadSize)) { + return false; + } + + return true; +} diff --git a/i2c_updater/pi-i2c/src/i2c.hpp b/i2c_updater/pi-i2c/src/i2c.hpp new file mode 100644 index 0000000..f190ace --- /dev/null +++ b/i2c_updater/pi-i2c/src/i2c.hpp @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _I2C_HPP_ +#define _I2C_HPP_ + +#include +#include + +class tI2c { +public: + tI2c(const std::string &Port); + + bool write(uint8_t ubAddr, const std::vector &vData); + + bool read(uint8_t ubAddr, uint8_t *pDest, uint32_t ulReadSize); + + template + bool read(uint8_t ubAddr, t_tContainer &Cont) { + return read(ubAddr, Cont.data(), Cont.size()); + } + +private: + int m_I2cHandle; +}; + +#endif // _I2C_HPP_ diff --git a/i2c_updater/pi-i2c/src/main.cpp b/i2c_updater/pi-i2c/src/main.cpp new file mode 100644 index 0000000..e25c1b8 --- /dev/null +++ b/i2c_updater/pi-i2c/src/main.cpp @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include "bitstream.hpp" +#include "i2c.hpp" +#include "endian.hpp" + +#define STATUS_BIT_BUSY (1 << 12) +#define STATUS_BIT_ERROR (1 << 13) + +static bool waitForNotBusy(tI2c &I2c, uint8_t ubFpgaAddr) +{ + using namespace std::chrono_literals; + + uint32_t ulStatus; + do { + std::this_thread::sleep_for(10ms); + I2c.write(ubFpgaAddr, {0x3C, 0x00, 0x00 , 0x00}); + bool isRead = I2c.read( + ubFpgaAddr, reinterpret_cast(&ulStatus), sizeof(ulStatus) + ); + if(!isRead) { + return false; + } + ulStatus = nEndian::bigToNative(ulStatus); + if(ulStatus & STATUS_BIT_ERROR) { + return false; + } + } while(ulStatus & STATUS_BIT_BUSY); + return true; +} + +static uint8_t reverseByte(uint8_t ubData) { + // https://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv + ubData = (ubData * 0x0202020202ULL & 0x010884422010ULL) % 1023; + return ubData; +} + +int main(int lArgCount, const char *pArgs[]) +{ + using namespace std::chrono_literals; + + if(lArgCount < 4) { + printf("Usage:\n\t%s i2cPort i2cSlaveAddrHex /path/to/cfg.bit\n", pArgs[0]); + printf("e.g.:\n\t%s /dev/i2c-1 5a file.bit\n", pArgs[0]); + return EXIT_FAILURE; + } + + std::optional Bitstream; + try { + Bitstream = tBitstream(pArgs[3]); + } + catch(const std::exception &Exc) { + printf( + "ERR: bitstream '%s' read fail: '%s'\n", pArgs[3], Exc.what() + ); + return EXIT_FAILURE; + } + + // Display some info about bitstream + printf("Successfully read bitstream:\n"); + for(const auto &Comment: Bitstream->m_vComments) { + printf("\t%s\n", Comment.c_str()); + } + + // Read the I2C address of the FPGA + auto FpgaAddr = std::stoi(pArgs[2], 0, 16); + + // Initialize I2C port + std::optional I2c; + try { + I2c = tI2c(pArgs[1]); + } + catch(const std::exception &Exc) { + printf( + "ERR: i2c port '%s' init fail: '%s'\n", pArgs[1], Exc.what() + ); + return EXIT_FAILURE; + } + + printf("Resetting FPGA...\n"); + // TODO: CRESET:=0 + std::this_thread::sleep_for(1s); + I2c->write(FpgaAddr, {0xA4, 0xC6, 0xF4, 0x8A}); + // TODO: CRESET:=1 + std::this_thread::sleep_for(10ms); + + // 1. Read ID (E0) + printf("Checking device id...\n"); + I2c->write(FpgaAddr, {0xE0, 0x00, 0x00, 0x00}); + std::array DevId; + bool isRead = I2c->read(FpgaAddr, DevId); + if(!isRead) { + printf("ERR: Can't read data from I2C device\n"); + return EXIT_FAILURE; + } + if(DevId != Bitstream->m_DeviceId) { + printf( + "ERR: DevId mismatch! Dev: %02X %02X %02X %02X, " + ".bit: %02X, %02X, %02X, %02X\n", + DevId[0], DevId[1], DevId[2], DevId[3], + Bitstream->m_DeviceId[0], Bitstream->m_DeviceId[1], + Bitstream->m_DeviceId[2], Bitstream->m_DeviceId[3] + ); + return EXIT_FAILURE; + } + + // 2. Enable configuration interface (C6) + printf("Initiating programming...\n"); + I2c->write(FpgaAddr, {0xC6, 0x00, 0x00, 0x00}); + std::this_thread::sleep_for(1ms); + + // 3. Read status register (3C) and wait for busy bit go to 0 + if(!waitForNotBusy(*I2c, FpgaAddr)) { + printf("ERR: status register has error bit set!\n"); + return EXIT_FAILURE; + } + + // 4. LSC_INIT_ADDRESS (46) + I2c->write(FpgaAddr, {0x46, 0x00, 0x00 , 0x00}); + std::this_thread::sleep_for(100ms); + + // 5. Program SRAM parts (82) + uint32_t lRowIdx; + for(const auto &Row: Bitstream->m_vProgramData) { + printf( + "\rProgramming row %5u/%5u...", + ++lRowIdx, Bitstream->m_vProgramData.size() + ); + fflush(stdout); + // Compose and send next SRAM packet + std::vector Packet = {0x82, 0x21, 0x00, 0x00}; + for(const auto &RowByte: Row) { + Packet.push_back(RowByte); + } + I2c->write(FpgaAddr, Packet); + std::this_thread::sleep_for(100us); + + // Some kind of dummy packet + I2c->write(FpgaAddr, {0x00, 0x00, 0x00, 0x00}); + std::this_thread::sleep_for(100us); + } + printf("\n"); + + // 6. Program usercode (C2) + printf("Programming usercode...\n"); + I2c->write(FpgaAddr, { + 0x46, 0x00, 0x00 , 0x00, + reverseByte(Bitstream->m_UserCode[3]), reverseByte(Bitstream->m_UserCode[2]), + reverseByte(Bitstream->m_UserCode[1]), reverseByte(Bitstream->m_UserCode[0]) + }); + + // 7. Program done (5E) + printf("Finalizing programming...\n"); + I2c->write(FpgaAddr, {0x5E, 0x00, 0x00 , 0x00}); + + // 8. Read status register (3C) and wait for busy bit go to 0 + if(!waitForNotBusy(*I2c, FpgaAddr)) { + printf("ERR: status register has error bit set!\n"); + return EXIT_FAILURE; + } + + // 9. Exit programming mode (26) + I2c->write(FpgaAddr, {0x26, 0x00, 0x00 , 0x00}); + + printf("All done!\n"); + return EXIT_SUCCESS; +} diff --git a/input/input.c b/input/input.c index b5fe840..735744b 100644 --- a/input/input.c +++ b/input/input.c @@ -25,9 +25,9 @@ char keymap_amiga[256] = { /*10*/ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x44, 0x63, 0x20, 0x21, /*20*/ 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x00, 0x60, 0x2B, 0x31, 0x32, 0x33, 0x34, /*30*/ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x61, 0x5D, 0x64, 0x40, NONE, 0x50, 0x51, 0x52, 0x53, 0x54, -/*40*/ 0x55, 0x56, 0x57, 0x58, 0x69, 0x5B, NONE, 0x3D, 0x3E, 0x3F, 0x4A, 0x2D, 0x2E, 0x2F, 0x4E, 0x1D, +/*40*/ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5B, NONE, 0x3D, 0x3E, 0x3F, 0x4A, 0x2D, 0x2E, 0x2F, 0x5E, 0x1D, /*50*/ 0x1E, 0x1F, 0x0F, 0x3C, NONE, NONE, 0x30, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, -/*60*/ 0x43, NONE, 0x5C, NONE, NONE, NONE, 0x5F, 0x4C, 0x5A, 0x4F, 0x4E, NONE, 0x4D, NONE, 0x0D, 0x46, +/*60*/ 0x43, NONE, 0x5C, NONE, 0x65, NONE, 0x5F, 0x4C, 0x5A, 0x4F, 0x4E, NONE, 0x4D, NONE, 0x0D, 0x46, /*70*/ NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, 0x66, 0x67, 0x67, /*80*/ NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, /*90*/ NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, diff --git a/m68k.h b/m68k.h index a7328cf..ca91b14 100644 --- a/m68k.h +++ b/m68k.h @@ -210,6 +210,7 @@ void m68k_write_memory_32(unsigned int address, unsigned int value); /* PiStorm speed hax */ void m68k_add_ram_range(uint32_t addr, uint32_t upper, unsigned char *ptr); void m68k_add_rom_range(uint32_t addr, uint32_t upper, unsigned char *ptr); +void m68k_clear_ranges(); /* Special call to simulate undocumented 68k behavior when move.l with a * predecrement destination mode is executed. diff --git a/m68kconf.h b/m68kconf.h index 4122af0..3dfbff8 100644 --- a/m68kconf.h +++ b/m68kconf.h @@ -75,7 +75,7 @@ * and m68k_read_pcrelative_xx() for PC-relative addressing. * If off, all read requests from the CPU will be redirected to m68k_read_xx() */ -#define M68K_SEPARATE_READS OPT_OFF +#define M68K_SEPARATE_READS OPT_ON /* If ON, the CPU will call m68k_write_32_pd() when it executes move.l with a * predecrement destination EA mode instead of m68k_write_32(). @@ -185,7 +185,7 @@ #define M68K_LOG_FILEHANDLE some_file_handle -#define M68K_EMULATE_PMMU OPT_ON +#define M68K_EMULATE_PMMU OPT_OFF /* ----------------------------- COMPATIBILITY ---------------------------- */ diff --git a/m68kcpu.c b/m68kcpu.c index da93430..0927005 100644 --- a/m68kcpu.c +++ b/m68kcpu.c @@ -1394,6 +1394,21 @@ void m68k_add_rom_range(uint32_t addr, uint32_t upper, unsigned char *ptr) } } +void m68k_clear_ranges() +{ + printf("[MUSASHI] Clearing all reads/write memory ranges.\n"); + for (int i = 0; i < 8; i++) { + read_upper[i] = 0; + read_addr[i] = 0; + read_data[i] = NULL; + write_upper[i] = 0; + write_addr[i] = 0; + write_data[i] = NULL; + } + write_ranges = 0; + read_ranges = 0; +} + /* ======================================================================== */ /* ============================== MAME STUFF ============================== */ /* ======================================================================== */ diff --git a/m68kcpu.h b/m68kcpu.h index 21d11ad..ef25ad1 100644 --- a/m68kcpu.h +++ b/m68kcpu.h @@ -1178,7 +1178,6 @@ static inline uint m68ki_read_imm_16(void) return m68ki_read_imm6_addr_slowpath(pc, cache); } - static inline uint m68ki_read_imm_8(void) { /* map read immediate 8 to read immediate 16 */ @@ -1227,15 +1226,7 @@ static inline uint m68ki_read_imm_32(void) return temp_val; #else - m68ki_set_fc(FLAG_S | FUNCTION_CODE_USER_PROGRAM); /* auto-disable (see m68kcpu.h) */ - m68ki_check_address_error(REG_PC, MODE_READ, FLAG_S | FUNCTION_CODE_USER_PROGRAM); /* auto-disable (see m68kcpu.h) */ - uint32_t address = ADDRESS_68K(REG_PC); REG_PC += 4; - for (int i = 0; i < read_ranges; i++) { - if(address >= read_addr[i] && address < read_upper[i]) { - return be32toh(((unsigned int *)(read_data[i] + (address - read_addr[i])))[0]); - } - } return m68k_read_immediate_32(address); #endif /* M68K_EMULATE_PREFETCH */ diff --git a/m68kfpu.c b/m68kfpu.c index b103541..68c3f6f 100644 --- a/m68kfpu.c +++ b/m68kfpu.c @@ -1446,6 +1446,7 @@ static void fpgen_rm_reg(uint16 w2) case 0x45: // FDSQRT case 0x41: // FSSQRT case 0x04: // FSQRT + case 0x05: // FSQRT { REG_FP[dst] = floatx80_sqrt(source, &status); SET_CONDITION_CODES(REG_FP[dst]); @@ -1453,6 +1454,7 @@ static void fpgen_rm_reg(uint16 w2) break; } case 0x06: // FLOGNP1 + case 0x07: // FLOGNP1 { REG_FP[dst] = floatx80_lognp1 (source, &status); SET_CONDITION_CODES(REG_FP[dst]); @@ -1474,6 +1476,7 @@ static void fpgen_rm_reg(uint16 w2) break; } case 0x0a: // FATAN + case 0x0b: // FATAN { REG_FP[dst] = floatx80_atan(source, &status); SET_CONDITION_CODES(REG_FP[dst]); @@ -1523,6 +1526,7 @@ static void fpgen_rm_reg(uint16 w2) break; } case 0x12: // FTENTOX + case 0x13: // FTENTOX { REG_FP[dst] = floatx80_tentox(source, &status); SET_CONDITION_CODES(REG_FP[dst]); @@ -1544,6 +1548,7 @@ static void fpgen_rm_reg(uint16 w2) break; } case 0x16: // FLOG2 + case 0x17: // FLOG2 { REG_FP[dst] = floatx80_log2(source, &status); SET_CONDITION_CODES(REG_FP[dst]); @@ -1570,6 +1575,7 @@ static void fpgen_rm_reg(uint16 w2) case 0x5e: // FDNEG case 0x5a: // FSNEG case 0x1a: // FNEG + case 0x1b: // FNEG { REG_FP[dst] = source; REG_FP[dst].high ^= 0x8000; @@ -1678,9 +1684,16 @@ static void fpgen_rm_reg(uint16 w2) USE_CYCLES(11); // ? (value is from FMUL) break; } - case 0x6a: // FDSUB + case 0x6c: // FDSUB case 0x68: // FSSUB case 0x28: // FSUB + case 0x29: // FSUB + case 0x2a: // FSUB + case 0x2b: // FSUB + case 0x2c: // FSUB + case 0x2d: // FSUB + case 0x2e: // FSUB + case 0x2f: // FSUB { REG_FP[dst] = floatx80_sub(REG_FP[dst], source, &status); SET_CONDITION_CODES(REG_FP[dst]); @@ -1704,6 +1717,9 @@ static void fpgen_rm_reg(uint16 w2) break; } case 0x38: // FCMP + case 0x39: // FCMP + case 0x3c: // FCMP + case 0x3d: // FCMP { floatx80 res; res = floatx80_sub(REG_FP[dst], source, &status); @@ -1712,6 +1728,9 @@ static void fpgen_rm_reg(uint16 w2) break; } case 0x3a: // FTST + case 0x3b: // FTST + case 0x3e: // FTST + case 0x3f: // FTST { floatx80 res; res = source; diff --git a/nprog_240.sh b/nprog_240.sh old mode 100644 new mode 100755 diff --git a/platforms/amiga/Disk.info b/platforms/amiga/Disk.info new file mode 100644 index 0000000..5c09bde Binary files /dev/null and b/platforms/amiga/Disk.info differ diff --git a/platforms/amiga/amiga-autoconf.c b/platforms/amiga/amiga-autoconf.c index ed958ea..c6945ce 100644 --- a/platforms/amiga/amiga-autoconf.c +++ b/platforms/amiga/amiga-autoconf.c @@ -1,35 +1,51 @@ // SPDX-License-Identifier: MIT #include "platforms/platforms.h" +#include "pistorm-dev/pistorm-dev-enums.h" #include "amiga-autoconf.h" #include #include #include +#include "a314/a314.h" #define Z2_Z2 0xC #define Z2_FAST 0x2 #define Z2_BOOTROM 0x1 +// PiStorm Zorro II AutoConfig Fast RAM ROM static unsigned char ac_fast_ram_rom[] = { Z2_Z2 | Z2_FAST, AC_MEM_SIZE_8MB, // 00/02, link into memory free list, 8 MB 0x6, 0x9, // 06/09, product id 0x8, 0x0, // 08/0a, preference to 8 MB space 0x0, 0x0, // 0c/0e, reserved - 0x0, 0x7, 0xD, 0xB, // 10/12/14/16, mfg id + PISTORM_AC_MANUF_ID, // Manufacturer ID 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x2, 0x0, // 18/.../26, serial 0x0, 0x0, 0x0, 0x0, // Optional BOOT ROM vector }; +// PiSCSI AutoConfig Device ROM unsigned char ac_piscsi_rom[] = { Z2_Z2 | Z2_BOOTROM, AC_MEM_SIZE_64KB, // 00/01, Z2, bootrom, 64 KB 0x6, 0xA, // 06/0A, product id 0x0, 0x0, // 00/0a, any space where it fits 0x0, 0x0, // 0c/0e, reserved - 0x0, 0x7, 0xD, 0xB, // 10/12/14/16, mfg id + PISTORM_AC_MANUF_ID, // Manufacturer ID 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x2, 0x1, // 18/.../26, serial 0x4, 0x0, 0x0, 0x0, // Optional BOOT ROM vector }; +// PiStorm Device Interaction ROM +unsigned char ac_pistorm_rom[] = { + Z2_Z2, AC_MEM_SIZE_64KB, // 00/01, Z2, bootrom, 64 KB + 0x6, 0xB, // 06/0B, product id + 0x0, 0x0, // 00/0a, any space where it fits + 0x0, 0x0, // 0c/0e, reserved + PISTORM_AC_MANUF_ID, // Manufacturer ID + 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x2, 0x2, // 18/.../26, serial + 0x4, 0x0, 0x0, 0x0, // Optional BOOT ROM vector +}; + +// A314 Emulation ROM static unsigned char ac_a314_rom[] = { 0xc, AC_MEM_SIZE_64KB, // 00/02, 64 kB 0xa, 0x3, // 04/06, product id @@ -40,6 +56,8 @@ static unsigned char ac_a314_rom[] = { 0x0, 0x0, 0x0, 0x0, // Optional BOOT ROM vector }; +extern unsigned int a314_base; + int ac_z2_current_pic = 0; int ac_z2_pic_count = 0; int ac_z2_done = 0; @@ -53,7 +71,7 @@ int ac_z3_done = 0; int ac_z3_type[AC_PIC_LIMIT]; int ac_z3_index[AC_PIC_LIMIT]; -uint32_t piscsi_base = 0; +uint32_t piscsi_base = 0, pistorm_dev_base = 0; extern uint8_t *piscsi_rom_ptr; unsigned char get_autoconf_size(int size) { @@ -88,6 +106,20 @@ unsigned char get_autoconf_size_ext(int size) { extern void adjust_ranges_amiga(struct emulator_config *cfg); +void autoconfig_reset_all() { + printf("[AUTOCONF] Resetting all autoconf data.\n"); + for (int i = 0; i < AC_PIC_LIMIT; i++) { + ac_z2_type[i] = ACTYPE_NONE; + ac_z3_type[i] = ACTYPE_NONE; + ac_z2_index[i] = 0; + ac_z3_index[i] = 0; + } + ac_z3_pic_count = 0; + ac_z2_pic_count = 0; + ac_z2_current_pic = 0; + ac_z3_current_pic = 0; +} + unsigned int autoconfig_read_memory_z3_8(struct emulator_config *cfg, unsigned int address) { int index = ac_z3_index[ac_z3_current_pic]; unsigned char val = 0; @@ -201,7 +233,7 @@ void autoconfig_write_memory_z3_8(struct emulator_config *cfg, unsigned int addr if (done) { nib_latch = 0; - printf("Address of Z3 autoconf RAM assigned to $%.8x [B]\n", ac_base[ac_z3_current_pic]); + printf("[AUTOCONF] Address of Z3 autoconf RAM assigned to $%.8x [B]\n", ac_base[ac_z3_current_pic]); cfg->map_offset[index] = ac_base[ac_z3_current_pic]; cfg->map_high[index] = cfg->map_offset[index] + cfg->map_size[index]; m68k_add_ram_range(cfg->map_offset[index], cfg->map_high[index], cfg->map_data[index]); @@ -233,7 +265,7 @@ void autoconfig_write_memory_z3_16(struct emulator_config *cfg, unsigned int add } if (done) { - printf("Address of Z3 autoconf RAM assigned to $%.8x [W]\n", ac_base[ac_z3_current_pic]); + printf("[AUTOCONF] Address of Z3 autoconf RAM assigned to $%.8x [W]\n", ac_base[ac_z3_current_pic]); cfg->map_offset[index] = ac_base[ac_z3_current_pic]; cfg->map_high[index] = cfg->map_offset[index] + cfg->map_size[index]; m68k_add_ram_range(cfg->map_offset[index], cfg->map_high[index], cfg->map_data[index]); @@ -247,6 +279,40 @@ void autoconfig_write_memory_z3_16(struct emulator_config *cfg, unsigned int add return; } +void add_z2_pic(uint8_t type, uint8_t index) { + if (ac_z2_pic_count < AC_PIC_LIMIT) { + ac_z2_type[ac_z2_pic_count] = type; + ac_z2_index[ac_z2_pic_count] = index; + ac_z2_pic_count++; + return; + } + printf("[AUTOCONF] Failed to add Z2 PIC of type %d, limit exceeded.\n", type); +} + +void remove_z2_pic(uint8_t type, uint8_t index) { + uint8_t pic_found = 0; + if (index) {} + + for (uint32_t i = 0; i < ac_z2_pic_count; i++) { + if (ac_z2_type[i] == type && !pic_found) { + pic_found = 1; + } + if (pic_found && i < AC_PIC_LIMIT - 1) { + ac_z2_type[i] = ac_z2_type[i + 1]; + ac_z2_index[i] = ac_z2_index[ i + 1]; + } + } + + if (pic_found) { + ac_z2_type[AC_PIC_LIMIT - 1] = ACTYPE_NONE; + ac_z2_index[AC_PIC_LIMIT - 1] = 0; + ac_z2_pic_count--; + } + else { + printf("[AUTOCONF] Tried to remove Z2 PIC of type %d, but it wasn't found.\n", type); + } +} + unsigned int autoconfig_read_memory_8(struct emulator_config *cfg, unsigned int address) { unsigned char *rom = NULL; unsigned char val = 0; @@ -261,6 +327,9 @@ unsigned int autoconfig_read_memory_8(struct emulator_config *cfg, unsigned int case ACTYPE_PISCSI: rom = ac_piscsi_rom; break; + case ACTYPE_PISTORM_DEV: + rom = ac_pistorm_rom; + break; default: return 0; break; @@ -295,11 +364,14 @@ void autoconfig_write_memory_8(struct emulator_config *cfg, unsigned int address base = &ac_base[ac_z2_current_pic]; break; case ACTYPE_A314: - //base = &a314_base; + base = &a314_base; break; case ACTYPE_PISCSI: base = &piscsi_base; break; + case ACTYPE_PISTORM_DEV: + base = &pistorm_dev_base; + break; default: break; } @@ -315,7 +387,7 @@ void autoconfig_write_memory_8(struct emulator_config *cfg, unsigned int address *base &= 0xff0fffff; *base |= (value & 0xf0) << (20 - 4); - if (ac_z2_type[ac_z2_current_pic] == ACTYPE_MAPFAST_Z2) { // fast ram + if (ac_z2_type[ac_z2_current_pic] == ACTYPE_A314) { //a314_set_mem_base_size(*base, cfg->map_size[ac_index[ac_z2_current_pic]]); } done = 1; @@ -330,15 +402,22 @@ void autoconfig_write_memory_8(struct emulator_config *cfg, unsigned int address case ACTYPE_MAPFAST_Z2: cfg->map_offset[index] = ac_base[ac_z2_current_pic]; cfg->map_high[index] = cfg->map_offset[index] + cfg->map_size[index]; - printf("Address of Z2 autoconf RAM assigned to $%.8x\n", ac_base[ac_z2_current_pic]); + printf("[AUTOCONF] Address of Z2 autoconf RAM assigned to $%.8X\n", ac_base[ac_z2_current_pic]); m68k_add_ram_range(cfg->map_offset[index], cfg->map_high[index], cfg->map_data[index]); - printf("Z2 PIC %d at $%.8lX-%.8lX, Size: %d MB\n", ac_z2_current_pic, cfg->map_offset[index], cfg->map_high[index], cfg->map_size[index] / SIZE_MEGA); + printf("[AUTOCONF] Z2 PIC %d at $%.8lX-%.8lX, Size: %d MB\n", ac_z2_current_pic, cfg->map_offset[index], cfg->map_high[index], cfg->map_size[index] / SIZE_MEGA); break; case ACTYPE_PISCSI: - printf("PiSCSI Z2 device assigned to $%.8x\n", piscsi_base); + printf("[AUTOCONF] PiSCSI Z2 device assigned to $%.8X\n", piscsi_base); //m68k_add_rom_range(piscsi_base + (16 * SIZE_KILO), piscsi_base + (32 * SIZE_KILO), piscsi_rom_ptr); break; + case ACTYPE_A314: + printf("[AUTOCONF] A314 emulation device assigned to $%.8X\n", a314_base); + break; + case ACTYPE_PISTORM_DEV: + printf("[AUTOCONF] PiStorm Interaction Z2 device assigned to $%.8X\n", pistorm_dev_base); + break; default: + printf("[!!!AUTOCONF] Some strange unknown Z2 device has been assigned to $%.8X?", *base); break; } ac_z2_current_pic++; diff --git a/platforms/amiga/amiga-autoconf.h b/platforms/amiga/amiga-autoconf.h index 0ca3db5..6ff40e8 100644 --- a/platforms/amiga/amiga-autoconf.h +++ b/platforms/amiga/amiga-autoconf.h @@ -5,7 +5,7 @@ #define AC_Z2_BASE 0xE80000 #define AC_Z3_BASE 0xFF000000 #define AC_SIZE (64 * 1024) -#define AC_PIC_LIMIT 8 +#define AC_PIC_LIMIT 16 #define AC_MEM_SIZE_8MB 0 #define AC_MEM_SIZE_64KB 1 @@ -26,10 +26,12 @@ #define AC_MEM_SIZE_EXT_RES 7 enum autoconf_types { + ACTYPE_NONE, ACTYPE_MAPFAST_Z2, ACTYPE_MAPFAST_Z3, ACTYPE_A314, ACTYPE_PISCSI, + ACTYPE_PISTORM_DEV, ACTYPE_NUM, }; @@ -89,3 +91,8 @@ void autoconfig_write_memory_8(struct emulator_config *cfg, unsigned int address unsigned int autoconfig_read_memory_z3_8(struct emulator_config *cfg, unsigned int address); void autoconfig_write_memory_z3_8(struct emulator_config *cfg, unsigned int address, unsigned int value); void autoconfig_write_memory_z3_16(struct emulator_config *cfg, unsigned int address, unsigned int value); + +void autoconfig_reset_all(); + +void add_z2_pic(uint8_t type, uint8_t index); +void remove_z2_pic(uint8_t type, uint8_t index); diff --git a/platforms/amiga/amiga-platform.c b/platforms/amiga/amiga-platform.c index e3e1a07..a395d8c 100644 --- a/platforms/amiga/amiga-platform.c +++ b/platforms/amiga/amiga-platform.c @@ -11,9 +11,12 @@ #include "net/pi-net.h" #include "piscsi/piscsi-enums.h" #include "piscsi/piscsi.h" +#include "pistorm-dev/pistorm-dev-enums.h" +#include "pistorm-dev/pistorm-dev.h" #include "platforms/platforms.h" #include "platforms/shared/rtc.h" #include "rtg/rtg.h" +#include "a314/a314.h" //#define DEBUG_AMIGA_PLATFORM @@ -23,10 +26,8 @@ #define DEBUG(...) #endif - int handle_register_read_amiga(unsigned int addr, unsigned char type, unsigned int *val); int handle_register_write_amiga(unsigned int addr, unsigned int value, unsigned char type); -int init_rtg_data(); extern int ac_z2_current_pic; extern int ac_z2_done; @@ -39,7 +40,7 @@ extern int ac_z3_pic_count; extern int ac_z3_done; extern int ac_z3_type[AC_PIC_LIMIT]; extern int ac_z3_index[AC_PIC_LIMIT]; -extern int gayle_emulation_enabled; +extern uint8_t gayle_emulation_enabled; char *z2_autoconf_id = "z2_autoconf_fast"; char *z2_autoconf_zap_id = "^2_autoconf_fast"; @@ -50,13 +51,15 @@ extern const char *op_type_names[OP_TYPE_NUM]; extern uint8_t cdtv_mode; extern uint8_t rtc_type; extern unsigned char cdtv_sram[32 * SIZE_KILO]; +extern unsigned int a314_base; #define min(a, b) (a < b) ? a : b #define max(a, b) (a > b) ? a : b -uint8_t rtg_enabled = 0, piscsi_enabled = 0, pinet_enabled = 0, kick13_mode = 0; +uint8_t rtg_enabled = 0, piscsi_enabled = 0, pinet_enabled = 0, kick13_mode = 0, pistorm_dev_enabled = 1; +uint8_t a314_emulation_enabled = 0; -extern uint32_t piscsi_base; +extern uint32_t piscsi_base, pistorm_dev_base; extern void stop_cpu_emulation(uint8_t disasm_cur); @@ -100,13 +103,39 @@ inline int custom_read_amiga(struct emulator_config *cfg, unsigned int addr, uns return -1; } - if (addr >= piscsi_base && addr < piscsi_base + (64 * SIZE_KILO)) { + if (pistorm_dev_enabled && addr >= pistorm_dev_base && addr < pistorm_dev_base + (64 * SIZE_KILO)) { + *val = handle_pistorm_dev_read(addr, type); + return 1; + } + + if (piscsi_enabled && addr >= piscsi_base && addr < piscsi_base + (64 * SIZE_KILO)) { //printf("[Amiga-Custom] %s read from PISCSI base @$%.8X.\n", op_type_names[type], addr); //stop_cpu_emulation(1); *val = handle_piscsi_read(addr, type); return 1; } + if (a314_emulation_enabled && addr >= a314_base && addr < a314_base + (64 * SIZE_KILO)) { + //printf("%s read from A314 @$%.8X\n", op_type_names[type], addr); + switch (type) { + case OP_TYPE_BYTE: + *val = a314_read_memory_8(addr - a314_base); + return 1; + break; + case OP_TYPE_WORD: + *val = a314_read_memory_16(addr - a314_base); + return 1; + break; + case OP_TYPE_LONGWORD: + *val = a314_read_memory_32(addr - a314_base); + return 1; + break; + default: + break; + } + return 1; + } + return -1; } @@ -162,12 +191,39 @@ inline int custom_write_amiga(struct emulator_config *cfg, unsigned int addr, un } } - if (addr >= piscsi_base && addr < piscsi_base + (64 * SIZE_KILO)) { + if (pistorm_dev_enabled && addr >= pistorm_dev_base && addr < pistorm_dev_base + (64 * SIZE_KILO)) { + handle_pistorm_dev_write(addr, val, type); + return 1; + } + + if (piscsi_enabled && addr >= piscsi_base && addr < piscsi_base + (64 * SIZE_KILO)) { //printf("[Amiga-Custom] %s write to PISCSI base @$%.8x: %.8X\n", op_type_names[type], addr, val); handle_piscsi_write(addr, val, type); return 1; } + if (a314_emulation_enabled && addr >= a314_base && addr < a314_base + (64 * SIZE_KILO)) { + //printf("%s write to A314 @$%.8X: %d\n", op_type_names[type], addr, val); + switch (type) { + case OP_TYPE_BYTE: + a314_write_memory_8(addr - a314_base, val); + return 1; + break; + case OP_TYPE_WORD: + // Not implemented in a314.cc + //a314_write_memory_16(addr, val); + return -1; + break; + case OP_TYPE_LONGWORD: + // Not implemented in a314.cc + // a314_write_memory_32(addr, val); + return -1; + break; + default: + break; + } + } + return -1; } @@ -260,7 +316,7 @@ int setup_platform_amiga(struct emulator_config *cfg) { cfg->map_id[index][0] = '^'; int resize_data = 0; if (cfg->map_size[index] > 8 * SIZE_MEGA) { - printf("Attempted to configure more than 8MB of Z2 Fast RAM, downsizng to 8MB.\n"); + printf("Attempted to configure more than 8MB of Z2 Fast RAM, downsizing to 8MB.\n"); resize_data = 8 * SIZE_MEGA; } else if(cfg->map_size[index] != 2 * SIZE_MEGA && cfg->map_size[index] != 4 * SIZE_MEGA && cfg->map_size[index] != 8 * SIZE_MEGA) { @@ -279,9 +335,10 @@ int setup_platform_amiga(struct emulator_config *cfg) { cfg->map_data[index] = (unsigned char *)malloc(cfg->map_size[index]); } printf("%dMB of Z2 Fast RAM configured at $%lx\n", cfg->map_size[index] / SIZE_MEGA, cfg->map_offset[index]); - ac_z2_type[ac_z2_pic_count] = ACTYPE_MAPFAST_Z2; - ac_z2_index[ac_z2_pic_count] = index; - ac_z2_pic_count++; + add_z2_pic(ACTYPE_MAPFAST_Z2, index); + //ac_z2_type[ac_z2_pic_count] = ACTYPE_MAPFAST_Z2; + //ac_z2_index[ac_z2_pic_count] = index; + //ac_z2_pic_count++; } else printf("No Z2 Fast RAM configured.\n"); @@ -333,14 +390,20 @@ int setup_platform_amiga(struct emulator_config *cfg) { } } + if (pistorm_dev_enabled) { + add_z2_pic(ACTYPE_PISTORM_DEV, 0); + } + return 0; } +#define CHKVAR(a) (strcmp(var, a) == 0) + void setvar_amiga(struct emulator_config *cfg, char *var, char *val) { if (!var) return; - if (strcmp(var, "enable_rtc_emulation") == 0) { + if CHKVAR("enable_rtc_emulation") { int8_t rtc_enabled = 0; if (!val || strlen(val) == 0) rtc_enabled = 1; @@ -351,20 +414,20 @@ void setvar_amiga(struct emulator_config *cfg, char *var, char *val) { configure_rtc_emulation_amiga(rtc_enabled); } } - if (strcmp(var, "hdd0") == 0) { + if CHKVAR("hdd0") { if (val && strlen(val) != 0) set_hard_drive_image_file_amiga(0, val); } - if (strcmp(var, "hdd1") == 0) { + if CHKVAR("hdd1") { if (val && strlen(val) != 0) set_hard_drive_image_file_amiga(1, val); } - if (strcmp(var, "cdtv") == 0) { + if CHKVAR("cdtv") { printf("[AMIGA] CDTV mode enabled.\n"); cdtv_mode = 1; } - if (strcmp(var, "rtg") == 0 && !rtg_enabled) { - if (init_rtg_data()) { + if (CHKVAR("rtg") && !rtg_enabled) { + if (init_rtg_data(cfg)) { printf("[AMIGA] RTG Enabled.\n"); rtg_enabled = 1; adjust_ranges_amiga(cfg); @@ -372,54 +435,73 @@ void setvar_amiga(struct emulator_config *cfg, char *var, char *val) { else printf("[AMIGA} Failed to enable RTG.\n"); } - if (strcmp(var, "kick13") == 0) { + if CHKVAR("kick13") { printf("[AMIGA] Kickstart 1.3 mode enabled, Z3 PICs will not be enumerated.\n"); kick13_mode = 1; } + if CHKVAR("a314") { + int32_t res = a314_init(); + if (res != 0) { + printf("[AMIGA] Failed to enable A314 emulation, error return code: %d.\n", res); + } else { + printf("[AMIGA] A314 emulation enabled.\n"); + add_z2_pic(ACTYPE_A314, 0); + a314_emulation_enabled = 1; + } + } + if CHKVAR("a314_conf") { + if (val && strlen(val) != 0) { + a314_set_config_file(val); + } + } // PiSCSI stuff - if (strcmp(var, "piscsi") == 0 && !piscsi_enabled) { + if (CHKVAR("piscsi") && !piscsi_enabled) { printf("[AMIGA] PISCSI Interface Enabled.\n"); piscsi_enabled = 1; piscsi_init(); - ac_z2_type[ac_z2_pic_count] = ACTYPE_PISCSI; - ac_z2_pic_count++; + add_z2_pic(ACTYPE_PISCSI, 0); adjust_ranges_amiga(cfg); } if (piscsi_enabled) { - if (strcmp(var, "piscsi0") == 0) { + if CHKVAR("piscsi0") { piscsi_map_drive(val, 0); } - if (strcmp(var, "piscsi1") == 0) { + if CHKVAR("piscsi1") { piscsi_map_drive(val, 1); } - if (strcmp(var, "piscsi2") == 0) { + if CHKVAR("piscsi2") { piscsi_map_drive(val, 2); } - if (strcmp(var, "piscsi3") == 0) { + if CHKVAR("piscsi3") { piscsi_map_drive(val, 3); } - if (strcmp(var, "piscsi4") == 0) { + if CHKVAR("piscsi4") { piscsi_map_drive(val, 4); } - if (strcmp(var, "piscsi5") == 0) { + if CHKVAR("piscsi5") { piscsi_map_drive(val, 5); } - if (strcmp(var, "piscsi6") == 0) { + if CHKVAR("piscsi6") { piscsi_map_drive(val, 6); } } // Pi-Net stuff - if (strcmp(var, "pi-net") == 0 && !pinet_enabled) { + if (CHKVAR("pi-net")&& !pinet_enabled) { printf("[AMIGA] PI-NET Interface Enabled.\n"); pinet_enabled = 1; pinet_init(val); adjust_ranges_amiga(cfg); } + if CHKVAR("no-pistorm-dev") { + pistorm_dev_enabled = 0; + printf("[AMIGA] Disabling PiStorm interaction device.\n"); + } + // RTC stuff - if (strcmp(var, "rtc_type") == 0) { + if CHKVAR("rtc_type") { if (val && strlen(val) != 0) { if (strcmp(val, "msm") == 0) { printf("[AMIGA] RTC type set to MSM.\n"); @@ -461,6 +543,28 @@ void shutdown_platform_amiga(struct emulator_config *cfg) { printf("Failed to write CDTV SRAM to disk.\n"); } } + if (cfg->platform->subsys) { + free(cfg->platform->subsys); + } + if (piscsi_enabled) { + piscsi_shutdown(); + piscsi_enabled = 0; + } + if (rtg_enabled) { + shutdown_rtg(); + rtg_enabled = 0; + } + if (pinet_enabled) { + pinet_enabled = 0; + } + if (a314_emulation_enabled) { + a314_emulation_enabled = 0; + } + + cdtv_mode = 0; + + autoconfig_reset_all(); + printf("[AMIGA] Platform shutdown completed.\n"); } void create_platform_amiga(struct platform_config *cfg, char *subsys) { diff --git a/platforms/amiga/amiga-registers.c b/platforms/amiga/amiga-registers.c index 056d4c9..5d2f887 100644 --- a/platforms/amiga/amiga-registers.c +++ b/platforms/amiga/amiga-registers.c @@ -5,7 +5,7 @@ #include "amiga-registers.h" uint8_t rtc_emulation_enabled = 1; -extern int gayle_emulation_enabled; +extern uint8_t gayle_emulation_enabled; void configure_rtc_emulation_amiga(uint8_t enabled) { if (enabled == rtc_emulation_enabled) diff --git a/platforms/amiga/amiga-registers.h b/platforms/amiga/amiga-registers.h index 81a3038..9882e6a 100644 --- a/platforms/amiga/amiga-registers.h +++ b/platforms/amiga/amiga-registers.h @@ -37,6 +37,7 @@ void adjust_gayle_1200(); #define CIAACRA 0xBFEE01 #define CIAACRB 0xBFEF01 #define POTGOR 0xDFF016 +#define SERDAT 0xDFF030 /* RAMSEY ADDRESSES */ #define RAMSEY_REG 0xDE0003 /* just a nibble, it should return 0x08 for defaults with 16MB */ diff --git a/platforms/amiga/build_hdf.sh b/platforms/amiga/build_hdf.sh new file mode 100755 index 0000000..91e784e --- /dev/null +++ b/platforms/amiga/build_hdf.sh @@ -0,0 +1,27 @@ +# Requires xdftool from amitools (https://github.com/cnvogelg/amitools/) +rm pistorm.hdf +rdbtool pistorm.hdf create size=2.5Mi + init rdb_cyls=2 +rdbtool pistorm.hdf add size=100% name=DH99 dostype=ffs +rdbtool pistorm.hdf fsadd dos1.bin fs=DOS1 +xdftool pistorm.hdf open part=DH99 + format PiStorm ffs +xdftool pistorm.hdf open part=DH99 + write Disk.info +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/PiSimple +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/PiStorm +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/PiStorm.info +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/libs13 +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/libs20 +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/libs13.info +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/libs20.info +xdftool pistorm.hdf open part=DH99 + write pistorm-dev/pistorm_dev_amiga/CopyMems +xdftool pistorm.hdf open part=DH99 + write ../../a314/software-amiga a314 +xdftool pistorm.hdf open part=DH99 + makedir net +xdftool pistorm.hdf open part=DH99 + write net/net_driver_amiga/pi-net.device net/pi-net.device +xdftool pistorm.hdf open part=DH99 + makedir scsi +xdftool pistorm.hdf open part=DH99 + write piscsi/device_driver_amiga/pi-scsi.device scsi/pi-scsi.device +xdftool pistorm.hdf open part=DH99 + makedir rtg +xdftool pistorm.hdf open part=DH99 + write "rtg/PiGFX Install" rtg +xdftool pistorm.hdf open part=DH99 + write "rtg/PiGFX Install.info" rtg +xdftool pistorm.hdf open part=DH99 + makedir "rtg/PiGFX Install/Files" +xdftool pistorm.hdf open part=DH99 + write rtg/rtg_driver_amiga/pigfx020.card "rtg/PiGFX Install/Files/pigfx020.card" +xdftool pistorm.hdf open part=DH99 + write rtg/rtg_driver_amiga/pigfx030.card "rtg/PiGFX Install/Files/pigfx030.card" +xdftool pistorm.hdf open part=DH99 + write rtg/rtg_driver_amiga/PiGFX.info "rtg/PiGFX Install/Files/PiGFX.info" diff --git a/platforms/amiga/dos1.bin b/platforms/amiga/dos1.bin new file mode 100644 index 0000000..4a8f78d Binary files /dev/null and b/platforms/amiga/dos1.bin differ diff --git a/platforms/amiga/hunk-reloc.c b/platforms/amiga/hunk-reloc.c index 70d7e2b..e7c296d 100644 --- a/platforms/amiga/hunk-reloc.c +++ b/platforms/amiga/hunk-reloc.c @@ -7,11 +7,15 @@ #include #include #include "hunk-reloc.h" +#include "piscsi/piscsi-enums.h" +#include "piscsi/piscsi.h" #ifdef FAKESTORM #define lseek64 lseek #endif +#define DEBUG_SPAMMY(...) +//#define DEBUG_SPAMMY printf #define DEBUG(...) //#define DEBUG printf @@ -53,10 +57,14 @@ int process_hunk(uint32_t index, struct hunk_info *info, FILE *f, struct hunk_re switch (index) { case HUNKTYPE_HEADER: - DEBUG("Processing hunk header.\n"); + DEBUG("[HUNK_RELOC] Processing hunk HEADER.\n"); do { READLW(discard, f); if (discard) { + if (info->libnames[info->num_libs]) { + free(info->libnames[info->num_libs]); + info->libnames[info->num_libs] = NULL; + } info->libnames[info->num_libs] = malloc(discard * 4); fread(info->libnames[info->num_libs], discard, 4, f); info->num_libs++; @@ -64,38 +72,48 @@ int process_hunk(uint32_t index, struct hunk_info *info, FILE *f, struct hunk_re } while (discard); READLW(info->table_size, f); - DEBUG("Table size: %d\n", info->table_size); + DEBUG("[HUNK_RELOC] [HEADER] Table size: %d\n", info->table_size); READLW(info->first_hunk, f); READLW(info->last_hunk, f); info->num_hunks = (info->last_hunk - info->first_hunk) + 1; - DEBUG("First: %d Last: %d Num: %d\n", info->first_hunk, info->last_hunk, info->num_hunks); + DEBUG("[HUNK_RELOC] [HEADER] First: %d Last: %d Num: %d\n", info->first_hunk, info->last_hunk, info->num_hunks); + if (info->hunk_sizes) { + free(info->hunk_sizes); + info->hunk_sizes = NULL; + } + if (info->hunk_offsets) { + free(info->hunk_offsets); + info->hunk_offsets = NULL; + } info->hunk_sizes = malloc(info->num_hunks * 4); info->hunk_offsets = malloc(info->num_hunks * 4); for (uint32_t i = 0; i < info->table_size; i++) { READLW(info->hunk_sizes[i], f); - DEBUG("Hunk %d: %d (%.8X)\n", i, info->hunk_sizes[i] * 4, info->hunk_sizes[i] * 4); + DEBUG("[HUNK_RELOC] [HEADER] Hunk %d: %d (%.8X)\n", i, info->hunk_sizes[i] * 4, info->hunk_sizes[i] * 4); } + info->header_size = (uint32_t)ftell(f) - file_offset; + DEBUG("[HUNK_RELOC] [HEADER] ~~~~~~~~~~~ Hunk HEADER size is %d ~~~~~~~~~~~~.\n", info->header_size); return 0; break; case HUNKTYPE_CODE: - DEBUG("Hunk %d: CODE.\n", info->current_hunk); + DEBUG("[HUNK_RELOC] Hunk %d: CODE.\n", info->current_hunk); READLW(discard, f); info->hunk_offsets[info->current_hunk] = ftell(f) - file_offset; - DEBUG("Code hunk size: %d (%.8X)\n", discard * 4, discard * 4); + DEBUG("[HUNK_RELOC] [CODE] Code hunk size: %d (%.8X)\n", discard * 4, discard * 4); fseek(f, discard * 4, SEEK_CUR); return 0; break; case HUNKTYPE_HUNK_RELOC32: - DEBUG("Hunk %d: RELOC32.\n", info->current_hunk); + DEBUG("[HUNK_RELOC] Hunk %d: RELOC32.\n", info->current_hunk); DEBUG("Processing Reloc32 hunk.\n"); do { READLW(discard, f); if (discard && discard != 0xFFFFFFFF) { READLW(cur_hunk, f); - DEBUG("Relocating %d offsets pointing to hunk %d.\n", discard, cur_hunk); + DEBUG("[HUNK_RELOC] [RELOC32] Relocating %d offsets pointing to hunk %d.\n", discard, cur_hunk); for(uint32_t i = 0; i < discard; i++) { READLW(offs32, f); - DEBUG("#%d: @%.8X in hunk %d\n", i + 1, offs32, cur_hunk); + DEBUG_SPAMMY("[HUNK_RELOC] [RELOC32] #%d: @%.8X in hunk %d\n", i + 1, offs32, cur_hunk); r[info->reloc_hunks].offset = offs32; r[info->reloc_hunks].src_hunk = info->current_hunk; r[info->reloc_hunks].target_hunk = cur_hunk; @@ -106,8 +124,8 @@ int process_hunk(uint32_t index, struct hunk_info *info, FILE *f, struct hunk_re return 0; break; case HUNKTYPE_SYMBOL: - DEBUG("Hunk %d: SYMBOL.\n", info->current_hunk); - DEBUG("Processing Symbol hunk.\n"); + DEBUG("[HUNK_RELOC] Hunk %d: SYMBOL.\n", info->current_hunk); + DEBUG("[HUNK_RELOC] [SYMBOL] Processing Symbol hunk.\n"); READLW(discard, f); do { if (discard) { @@ -115,34 +133,34 @@ int process_hunk(uint32_t index, struct hunk_info *info, FILE *f, struct hunk_re memset(sstr, 0x00, 256); fread(sstr, discard, 4, f); READLW(discard, f); - DEBUG("Symbol: %s - %.8X\n", sstr, discard); + DEBUG("[HUNK_RELOC] [SYMBOL] Symbol: %s - %.8X\n", sstr, discard); } READLW(discard, f); } while (discard); return 0; break; case HUNKTYPE_BSS: - DEBUG("Hunk %d: BSS.\n", info->current_hunk); + DEBUG("[HUNK_RELOC] Hunk %d: BSS.\n", info->current_hunk); READLW(discard, f); info->hunk_offsets[info->current_hunk] = ftell(f) - file_offset; - DEBUG("Skipping BSS hunk. Size: %d\n", discard * 4); + DEBUG("[HUNK_RELOC] [BSS] Skipping BSS hunk. Size: %d\n", discard * 4); add_size += (discard * 4); return 0; case HUNKTYPE_DATA: - DEBUG("Hunk %d: DATA.\n", info->current_hunk); + DEBUG("[HUNK_RELOC] Hunk %d: DATA.\n", info->current_hunk); READLW(discard, f); info->hunk_offsets[info->current_hunk] = ftell(f) - file_offset; - DEBUG("Skipping data hunk. Size: %d.\n", discard * 4); + DEBUG("[HUNK_RELOC] [DATA] Skipping data hunk. Size: %d.\n", discard * 4); fseek(f, discard * 4, SEEK_CUR); return 0; break; case HUNKTYPE_END: - DEBUG("END: Ending hunk %d.\n", info->current_hunk); + DEBUG("[HUNK_RELOC] END: Ending hunk %d.\n", info->current_hunk); info->current_hunk++; return 0; break; default: - DEBUG("Unknown hunk type %.8X! Can't process!\n", index); + DEBUG("[!!!HUNK_RELOC] Unknown hunk type %.8X! Can't process!\n", index); break; } @@ -155,13 +173,13 @@ void reloc_hunk(struct hunk_reloc *h, uint8_t *buf, struct hunk_info *i) { uint32_t src = be32toh(*src_ptr); uint32_t dst = src + i->base_offset + rel; - DEBUG("%.8X -> %.8X\n", src, dst); + DEBUG_SPAMMY("[HUNK-RELOC] %.8X -> %.8X\n", src, dst); *src_ptr = htobe32(dst); } void process_hunks(FILE *in, struct hunk_info *h_info, struct hunk_reloc *r, uint32_t offset) { READLW(lw, in); - DEBUG("Hunk ID: %.8X (%s)\n", lw, hunk_id_name(lw)); + DEBUG_SPAMMY("Hunk ID: %.8X (%s)\n", lw, hunk_id_name(lw)); file_offset = offset; add_size = 0; @@ -177,12 +195,12 @@ void process_hunks(FILE *in, struct hunk_info *h_info, struct hunk_reloc *r, uin } void reloc_hunks(struct hunk_reloc *r, uint8_t *buf, struct hunk_info *h_info) { - DEBUG("Relocating %d offsets.\n", h_info->reloc_hunks); + DEBUG("[HUNK-RELOC] Relocating %d offsets.\n", h_info->reloc_hunks); for (uint32_t i = 0; i < h_info->reloc_hunks; i++) { - DEBUG("Relocating offset %d.\n", i); + DEBUG_SPAMMY("[HUNK-RELOC] Relocating offset %d.\n", i); reloc_hunk(&r[i], buf, h_info); } - DEBUG("Done relocating offsets.\n"); + DEBUG("[HUNK-RELOC] Done relocating offsets.\n"); } struct LoadSegBlock { @@ -191,19 +209,22 @@ struct LoadSegBlock { int32_t lsb_ChkSum; uint32_t lsb_HostID; uint32_t lsb_Next; - uint32_t lsb_LoadData[123]; // Assumes 512 byte blocks + uint32_t lsb_LoadData[PISCSI_MAX_BLOCK_SIZE / 4]; }; #define LOADSEG_IDENTIFIER 0x4C534547 -int load_lseg(int fd, uint8_t **buf_p, struct hunk_info *i, struct hunk_reloc *relocs) { +int load_lseg(int fd, uint8_t **buf_p, struct hunk_info *i, struct hunk_reloc *relocs, uint32_t block_size) { if (fd == -1) return -1; + + if (block_size == 0) + block_size = 512; - uint8_t *block = malloc(512); + uint8_t *block = malloc(block_size); uint32_t next_blk = 0; struct LoadSegBlock *lsb = (struct LoadSegBlock *)block; - read(fd, block, 512); + read(fd, block, block_size); if (BE(lsb->lsb_ID) != LOADSEG_IDENTIFIER) { DEBUG("[LOAD_LSEG] Attempted to load a non LSEG-block: %.8X", BE(lsb->lsb_ID)); goto fail; @@ -218,9 +239,9 @@ int load_lseg(int fd, uint8_t **buf_p, struct hunk_info *i, struct hunk_reloc *r next_blk = BE(lsb->lsb_Next); do { next_blk = BE(lsb->lsb_Next); - fwrite(lsb->lsb_LoadData, 4, 123, out); - lseek64(fd, next_blk * 512, SEEK_SET); - read(fd, block, 512); + fwrite(lsb->lsb_LoadData, 1, block_size - 20, out); + lseek64(fd, next_blk * block_size, SEEK_SET); + read(fd, block, block_size); } while (next_blk != 0xFFFFFFFF); uint32_t file_size = ftell(out); @@ -243,3 +264,28 @@ fail:; return -1; } + +int load_fs(struct piscsi_fs *fs, char *dosID) { + char filename[256]; + memset(filename, 0x00, 256); + sprintf(filename, "./data/fs/%c%c%c.%d", dosID[0], dosID[1], dosID[2], dosID[3]); + + FILE *in = fopen(filename, "rb"); + if (in == NULL) + return -1; + + fseek(in, 0, SEEK_END); + uint32_t file_size = ftell(in); + fseek(in, 0, SEEK_SET); + + fs->binary_data = malloc(file_size); + fread(fs->binary_data, file_size, 1, in); + fseek(in, 0, SEEK_SET); + process_hunks(in, &fs->h_info, fs->relocs, 0x0); + fs->h_info.byte_size = file_size; + fs->h_info.alloc_size = file_size + add_size; + + fclose(in); + + return 0; +} diff --git a/platforms/amiga/hunk-reloc.h b/platforms/amiga/hunk-reloc.h index ef64444..1e53492 100644 --- a/platforms/amiga/hunk-reloc.h +++ b/platforms/amiga/hunk-reloc.h @@ -13,7 +13,7 @@ struct hunk_info { uint8_t *libnames[256]; uint32_t table_size, byte_size, alloc_size; uint32_t base_offset; - uint32_t first_hunk, last_hunk, num_hunks; + uint32_t first_hunk, last_hunk, num_hunks, header_size; uint32_t reloc_hunks; uint32_t *hunk_offsets; uint32_t *hunk_sizes; @@ -30,7 +30,7 @@ enum hunk_types { }; int process_hunk(uint32_t index, struct hunk_info *info, FILE *f, struct hunk_reloc *r); -int load_lseg(int fd, uint8_t **buf_p, struct hunk_info *i, struct hunk_reloc *relocs); +int load_lseg(int fd, uint8_t **buf_p, struct hunk_info *i, struct hunk_reloc *relocs, uint32_t block_size); void reloc_hunk(struct hunk_reloc *h, uint8_t *buf, struct hunk_info *i); void process_hunks(FILE *in, struct hunk_info *h_info, struct hunk_reloc *r, uint32_t offset); diff --git a/platforms/amiga/net/net_driver_amiga/build.sh b/platforms/amiga/net/net_driver_amiga/build.sh index 302ffc5..7ee836d 100644 --- a/platforms/amiga/net/net_driver_amiga/build.sh +++ b/platforms/amiga/net/net_driver_amiga/build.sh @@ -1 +1 @@ -m68k-amigaos-gcc -m68020 -O2 -o pi-net.device -ramiga-dev -noixemul -fbaserel pi-net-amiga.c -ldebug -lamiga +m68k-amigaos-gcc pi-net-amiga-2.c -O2 -o pi-net.device -m68000 -Wall -Wextra -Wno-unused-parameter -fomit-frame-pointer -nostartfiles -lm -ldebug diff --git a/platforms/amiga/net/net_driver_amiga/pi-net-amiga-2.c b/platforms/amiga/net/net_driver_amiga/pi-net-amiga-2.c new file mode 100644 index 0000000..c209a41 --- /dev/null +++ b/platforms/amiga/net/net_driver_amiga/pi-net-amiga-2.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +//#include +#include +//#include +#include "sana2.h" +#include "../pi-net-enums.h" + +#include +#include +#include + +#define STR(s) #s +#define XSTR(s) STR(s) + +#define DEVICE_NAME "pi-net.device" +#define DEVICE_DATE "(14 May 2021)" +#define DEVICE_ID_STRING "Pi-NET " XSTR(DEVICE_VERSION) "." XSTR(DEVICE_REVISION) " " DEVICE_DATE +#define DEVICE_VERSION 43 +#define DEVICE_REVISION 10 +#define DEVICE_PRIORITY 0 + +#pragma pack(4) +struct pinet_base { + struct Device* pi_dev; + struct Unit unit; + uint8_t MAC[6]; + uint8_t IP[4]; + + struct List read_list; + struct SignalSemaphore read_list_sem; +}; + +struct ExecBase* SysBase = NULL; +uint8_t *saved_seg_list; +uint8_t is_open; + +#define WRITESHORT(cmd, val) *(unsigned short *)((unsigned long)(PINET_OFFSET + cmd)) = val; +#define WRITELONG(cmd, val) *(unsigned long *)((unsigned long)(PINET_OFFSET + cmd)) = val; +#define WRITEBYTE(cmd, val) *(unsigned char *)((unsigned long)(PINET_OFFSET + cmd)) = val; + +#define READBYTE(cmd, var) var = *(volatile unsigned char *)(PINET_OFFSET + cmd); +#define READSHORT(cmd, var) var = *(volatile unsigned short *)(PINET_OFFSET + cmd); +#define READLONG(cmd, var) var = *(volatile unsigned long *)(PINET_OFFSET + cmd); + +int __attribute__((no_reorder)) _start() +{ + return -1; +} + +asm("romtag: \n" + " dc.w "XSTR(RTC_MATCHWORD)" \n" + " dc.l romtag \n" + " dc.l endcode \n" + " dc.b "XSTR(RTF_AUTOINIT)" \n" + " dc.b "XSTR(DEVICE_VERSION)" \n" + " dc.b "XSTR(NT_DEVICE)" \n" + " dc.b "XSTR(DEVICE_PRIORITY)" \n" + " dc.l _device_name \n" + " dc.l _device_id_string \n" + " dc.l _auto_init_tables \n" + "endcode: \n"); +char device_name[] = DEVICE_NAME; +char device_id_string[] = DEVICE_ID_STRING; + +typedef struct BufferManagement +{ + struct MinNode bm_Node; + BOOL (*bm_CopyFromBuffer)(void* a __asm("a0"), void* b __asm("a1"), long c __asm("d0")); + BOOL (*bm_CopyToBuffer)(void* a __asm("a0"), void* b __asm("a1"), long c __asm("d0")); +} BufferManagement; + +struct pinet_base *dev_base = NULL; + +//#define exit(...) +//#define debug(...) +#define kprintf(...) + +static struct Library __attribute__((used)) *init_device(struct Device* dev) { + uint8_t *p; + uint32_t i; + int32_t ok; + + // Unused variables + if (ok || i || p) { } + + SysBase = *(struct ExecBase **)4L; + + kprintf("Initializing net device.\n"); + + dev_base = AllocMem(sizeof(struct pinet_base), MEMF_PUBLIC | MEMF_CLEAR); + dev_base->pi_dev = dev; + + kprintf("Grabbing MAC.\n"); + for (int i = 0; i < 6; i++) { + READBYTE((PINET_CMD_MAC + i), dev_base->MAC[i]); + } + kprintf("Grabbing IP.\n"); + for (int i = 0; i < 4; i++) { + READBYTE((PINET_CMD_IP + i), dev_base->IP[i]); + } + + return (struct Library *)dev; +} + +static uint8_t* __attribute__((used)) expunge(void) { + kprintf("Cleaning up.\n"); + FreeMem(dev_base, sizeof(struct pinet_base)); + return 0; +} + +static void __attribute__((used)) open(struct IORequest *io, uint32_t num, uint32_t flags) { + struct IOSana2Req *ioreq = (struct IOSana2Req *)io; + int32_t ok = 0, ret = IOERR_OPENFAIL; + struct BufferManagement *bm; + + kprintf("Opening net device %ld.\n", num); + dev_base->unit.unit_OpenCnt++; + + // Unused variables + if (bm || ok) { } + + if (num == 0 && dev_base->unit.unit_OpenCnt == 1) { + //kprintf("Trying to alloc buffer management.\n"); + //if ((bm = (struct BufferManagement*)AllocVec(sizeof(struct BufferManagement), MEMF_CLEAR | MEMF_PUBLIC))) { + //kprintf("Setting up buffer copy funcs (1).\n"); + //bm->bm_CopyToBuffer = (BOOL *)GetTagData(S2_CopyToBuff, 0, (struct TagItem *)ioreq->ios2_BufferManagement); + //kprintf("Setting up buffer copy funcs (2).\n"); + //bm->bm_CopyFromBuffer = (BOOL *)GetTagData(S2_CopyFromBuff, 0, (struct TagItem *)ioreq->ios2_BufferManagement); + + kprintf("Doing more things.\n"); + ioreq->ios2_BufferManagement = NULL;//(VOID *)bm; + ioreq->ios2_Req.io_Error = 0; + ioreq->ios2_Req.io_Unit = (struct Unit *)&dev_base->unit; + ioreq->ios2_Req.io_Device = (struct Device *)dev_base->pi_dev; + + kprintf("New list.\n"); + + NewList(&dev_base->read_list); + InitSemaphore(&dev_base->read_list_sem); + + ret = 0; + ok = 1; + //} + } + + if (ret == IOERR_OPENFAIL) { + kprintf("Failed to open device. Already open?\n"); + } + else { + kprintf("Device opened, yay.\n"); + } + ioreq->ios2_Req.io_Error = ret; + ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + + kprintf("Opened device, return code: %ld\n", ret); +} + +static uint8_t* __attribute__((used)) close(struct IORequest *io) { + return 0; +} + +uint32_t pinet_read_frame(struct IOSana2Req *ioreq) { + return 0; +} + +void pinet_write_frame(struct IOSana2Req *ioreq) { +} + + +static void __attribute__((used)) begin_io(struct IORequest *io) { + struct IOSana2Req *ioreq = (struct IOSana2Req *)io; + ULONG unit = (ULONG)ioreq->ios2_Req.io_Unit; + int mtu; + + ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE; + ioreq->ios2_Req.io_Error = S2ERR_NO_ERROR; + ioreq->ios2_WireError = S2WERR_GENERIC_ERROR; + + //D(("BeginIO command %ld unit %ld\n",(LONG)ioreq->ios2_Req.io_Command,unit)); + + // Unused variables + if (mtu || unit) {} + + switch( ioreq->ios2_Req.io_Command ) { + case CMD_READ: + kprintf("Read\n"); + if (pinet_read_frame(ioreq) != 0) { + ioreq->ios2_Req.io_Error = S2ERR_BAD_ARGUMENT; + ioreq->ios2_WireError = S2WERR_BUFF_ERROR; + } + ioreq = NULL; + break; + case S2_BROADCAST: + kprintf("Broadcast\n"); + if (ioreq->ios2_DstAddr) { + for (int i = 0; i < ADDRFIELDSIZE; i++) { + ioreq->ios2_DstAddr[i] = 0xFF; + } + } else { + kprintf("Invalid ios2_DstAddr\n"); + } + /* Fallthrough */ + case CMD_WRITE: { + kprintf("Write\n"); + pinet_write_frame(ioreq); + break; + } + + case S2_READORPHAN: + ioreq->ios2_Req.io_Flags &= ~SANA2IOF_QUICK; + ioreq = NULL; + break; + case S2_ONLINE: + case S2_OFFLINE: + case S2_CONFIGINTERFACE: /* forward request */ + break; + + case S2_GETSTATIONADDRESS: + for (int i = 0; i < ADDRFIELDSIZE; i++) { + ioreq->ios2_SrcAddr[i] = dev_base->MAC[i]; + ioreq->ios2_DstAddr[i] = dev_base->MAC[i]; + } + break; + case S2_DEVICEQUERY: { + struct Sana2DeviceQuery *devquery; + + devquery = ioreq->ios2_StatData; + devquery->DevQueryFormat = 0; + devquery->DeviceLevel = 0; + + if (devquery->SizeAvailable >= 18) + devquery->AddrFieldSize = ADDRFIELDSIZE * 8; + if (devquery->SizeAvailable >= 22) + devquery->MTU = 1500; + if (devquery->SizeAvailable >= 26) + devquery->BPS = 1000 * 1000 * 100; + if (devquery->SizeAvailable >= 30) + devquery->HardwareType = S2WireType_Ethernet; + + devquery->SizeSupplied = (devquery->SizeAvailable < 30) ? devquery->SizeAvailable : 30; + break; + } + case S2_GETSPECIALSTATS: { + struct Sana2SpecialStatHeader *s2ssh = (struct Sana2SpecialStatHeader *)ioreq->ios2_StatData; + s2ssh->RecordCountSupplied = 0; + break; + } + default: { + uint8_t cmd = ioreq->ios2_Req.io_Command; + if (cmd) {} + kprintf("Unknown/unhandled IO command %lx\n", cmd); + ioreq->ios2_Req.io_Error = S2ERR_NOT_SUPPORTED; + ioreq->ios2_WireError = S2WERR_GENERIC_ERROR; + break; + } + } + + if (ioreq) { + if (!(ioreq->ios2_Req.io_Flags & SANA2IOF_QUICK)) { + ReplyMsg((struct Message *)ioreq); + } else { + ioreq->ios2_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + } + } +} + +static uint32_t __attribute__((used)) abort_io(struct IORequest* ioreq) { + struct IOSana2Req* ios2 = (struct IOSana2Req*)ioreq; + + if (!ioreq) return IOERR_NOCMD; + ioreq->io_Error = IOERR_ABORTED; + ios2->ios2_WireError = 0; + + return IOERR_ABORTED; +} + +static uint32_t device_vectors[] = { + (uint32_t)open, + (uint32_t)close, + (uint32_t)expunge, + 0, + (uint32_t)begin_io, + (uint32_t)abort_io, + -1 +}; + +const uint32_t auto_init_tables[4] = { + sizeof(struct Library), + (uint32_t)device_vectors, + 0, + (uint32_t)init_device +}; diff --git a/platforms/amiga/net/net_driver_amiga/pi-net.device b/platforms/amiga/net/net_driver_amiga/pi-net.device index 96f8b30..84d3fd1 100644 Binary files a/platforms/amiga/net/net_driver_amiga/pi-net.device and b/platforms/amiga/net/net_driver_amiga/pi-net.device differ diff --git a/platforms/amiga/net/pi-net-enums.h b/platforms/amiga/net/pi-net-enums.h index baa1b36..0e20d51 100644 --- a/platforms/amiga/net/pi-net-enums.h +++ b/platforms/amiga/net/pi-net-enums.h @@ -4,11 +4,6 @@ #define PINET_REGSIZE 0x00010000 #define PINET_UPPER 0x80020000 -/*enum piscsi_stuff { - PISCSI_BLOCK_SIZE = 512, - PISCSI_TRACK_SECTORS = 2048, -};*/ - #define ADDRFIELDSIZE 6 #define ETH_HDR_SIZE 14 diff --git a/platforms/amiga/net/pi-net.c b/platforms/amiga/net/pi-net.c index de3650f..bc3b87c 100644 --- a/platforms/amiga/net/pi-net.c +++ b/platforms/amiga/net/pi-net.c @@ -23,6 +23,10 @@ void pinet_init(char *dev) { (void)dev; } +void pinet_shutdown() { + // Aaahh! +} + uint8_t PI_MAC[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t PI_IP[4] = { 192, 168, 1, 9 }; diff --git a/platforms/amiga/net/pi-net.h b/platforms/amiga/net/pi-net.h index 5bf64e3..a3621cc 100644 --- a/platforms/amiga/net/pi-net.h +++ b/platforms/amiga/net/pi-net.h @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT void pinet_init(char *dev); +void pinet_shutdown(); void handle_pinet_write(uint32_t addr, uint32_t val, uint8_t type); uint32_t handle_pinet_read(uint32_t addr, uint8_t type); diff --git a/platforms/amiga/piscsi/device_driver_amiga/bootrom b/platforms/amiga/piscsi/device_driver_amiga/bootrom index fe3d33a..83b3bbf 100644 Binary files a/platforms/amiga/piscsi/device_driver_amiga/bootrom and b/platforms/amiga/piscsi/device_driver_amiga/bootrom differ diff --git a/platforms/amiga/piscsi/device_driver_amiga/bootrom.s b/platforms/amiga/piscsi/device_driver_amiga/bootrom.s index a0603d1..2014567 100644 --- a/platforms/amiga/piscsi/device_driver_amiga/bootrom.s +++ b/platforms/amiga/piscsi/device_driver_amiga/bootrom.s @@ -19,6 +19,7 @@ INCLUDE "exec/nodes.i" INCLUDE "exec/resident.i" INCLUDE "libraries/configvars.i" + INCLUDE "libraries/expansionbase.i" ; LVO's resolved by linking with library amiga.lib XREF _LVOFindResident @@ -64,6 +65,8 @@ OpenLibrary EQU -552 CloseLibrary EQU -414 OpenResource EQU -$1F2 AddResource EQU -$1E6 +Enqueue EQU -$10E +AddMemList EQU -$26A ; Expansion stuff MakeDosNode EQU -144 @@ -85,6 +88,8 @@ PiSCSINextFS EQU $80000064 PiSCSICopyFS EQU $80000068 PiSCSIFSSize EQU $8000006C PiSCSISetFSH EQU $80000070 +PiSCSILoadFS EQU $80000084 +PiSCSIGetFSInfo EQU $80000088 PiSCSIDbg1 EQU $80001010 PiSCSIDbg2 EQU $80001014 PiSCSIDbg3 EQU $80001018 @@ -160,7 +165,7 @@ DiagEntry: nop nop nop - move.l #1,PiSCSIDebugMe + move.l #1,PiSCSIDebugMe move.l a3,PiSCSIAddr1 nop nop @@ -207,18 +212,16 @@ endpatches: BootEntry: align 2 - move.l #2,PiSCSIDebugMe - nop - nop - nop - nop - nop - - lea DosName(PC),a1 ; 'dos.library',0 - jsr FindResident(a6) ; find the DOS resident tag - move.l d0,a0 ; in order to bootstrap - move.l RT_INIT(A0),a0 ; set vector to DOS INIT - jsr (a0) ; and initialize DOS + move.l #2,PiSCSIDebugMe + lea DosName(pc),a1 + jsr FindResident(a6) + tst.l d0 + beq.b .End + move.l d0,a0 + move.l RT_INIT(a0),a0 + jmp (a0) +.End + moveq.l #1,d0 ; indicate "success" rts * @@ -259,9 +262,26 @@ Init: ; After Diag patching, our romtag will point to this move.l a6,-(a7) ; Push A6 to stack move.w #$00B8,$dff09a ; Disable interrupts during init move.l #3,PiSCSIDebugMe + move.l a3,PiSCSIAddr4 - move.l #11,PiSCSIDebugMe movea.l 4,a6 + + move.l $10000040,d1 + move.l #$feffeeff,$10000040 + move.l $10000040,d0 + cmp.l #$feffeeff,d0 + bne.s NoZ3 + move.l d1,$10000040 + + move.l #$8000000,d0 ; Add some Z3 fast RAM if it hasn't been moved (Kick 1.3) + move.l #$405,d1 + move.l #10,d2 + move.l #$10000000,a0 + move.l #0,a1 + jsr AddMemList(a6) + +NoZ3: + move.l #11,PiSCSIDebugMe lea LibName(pc),a1 jsr FindResident(a6) move.l #10,PiSCSIDebugMe @@ -280,31 +300,91 @@ Init: ; After Diag patching, our romtag will point to this move.l d0,a1 move.l #0,d1 movea.l 4,a6 - add.l #$02c,a1 + add.l #$028,a1 jsr InitResident(a6) ; Initialize the PiSCSI driver SkipDriverLoad: move.l #9,PiSCSIDebugMe - bra.w LoadFileSystems + jsr LoadFileSystems(pc) FSLoadExit: lea ExpansionName(pc),a1 moveq #0,d0 jsr OpenLibrary(a6) ; Open expansion.library to make this work, somehow + move.l a6,a4 move.l d0,a6 move.l #7,PiSCSIDebugMe PartitionLoop: move.l PiSCSIGetPart,d0 ; Get the available partition in the current slot - beq.s EndPartitions ; If the next partition returns 0, there's no additional partitions + beq.w EndPartitions ; If the next partition returns 0, there's no additional partitions move.l d0,a0 jsr MakeDosNode(a6) + cmp.l #0,PiSCSIGetFSInfo + beq.s SkipLoadFS + + move.l d0,PiSCSILoadFS ; Attempt to load the file system driver from data/fs + cmp.l #$FFFFFFFF,PiSCSIAddr4 + beq SkipLoadFS + + jsr LoadFileSystems(pc) + +SkipLoadFS: move.l d0,PiSCSISetFSH + move.l d0,PiSCSIAddr2 ; Put DeviceNode address in PiSCSIAddr2, because I'm useless move.l d0,a0 move.l PiSCSIGetPrio,d0 move.l #0,d1 move.l PiSCSIAddr1,a1 - jsr AddBootNode(a6) + +* Uncomment these lines to test AddDosNode/Enqueue stuff +* Or comment them out all the way down to and including SkipEnqueue: to use the AddBootNode method instead. + cmp.l #-128,d0 + bne.s EnqueueNode + +* BOOL AddDosNode( LONG bootPri, ULONG flags, struct DeviceNode *deviceNode ); +* amicall(ExpansionBase, 0x96, AddDosNode(d0,d1,a0)) + move.l #38,PiSCSIDebugMe + jsr AddDosNode(a6) + bra.w SkipEnqueue +* VOID Enqueue( struct List *list, struct Node *node ); +* amicall(SysBase, 0x10e, Enqueue(a0,a1)) + +EnqueueNode: + exg a6,a4 + ;move.l #35,PiSCSIDebugMe + ;move.l #BootNode_SIZEOF,PiSCSIDebugMe + ;move.l #NT_BOOTNODE,PiSCSIDebugMe + ;move.l #LN_TYPE,PiSCSIDebugMe + ;move.l #LN_PRI,PiSCSIDebugMe + ;move.l #LN_NAME,PiSCSIDebugMe + ;move.l #eb_MountList,PiSCSIDebugMe + ;move.l #35,PiSCSIDebugMe + + move.l #BootNode_SIZEOF,d0 + move.l #$10001,d1 + jsr AllocMem(a6) ; Allocate memory for the BootNode + + move.l d0,PiSCSIAddr3 + move.l #36,PiSCSIDebugMe + + move.l d0,a1 + move.b #NT_BOOTNODE,LN_TYPE(a1) + move.l PiSCSIGetPrio,d0 + move.b d0,LN_PRI(a1) + move.l PiSCSIAddr2,bn_DeviceNode(a1) + move.l PiSCSIAddr1,LN_NAME(a1) + + lea eb_MountList(a4),a0 + jsr Enqueue(a6) + exg a6,a4 + +SkipEnqueue: + +* BOOL AddBootNode( LONG bootPri, ULONG flags, struct DeviceNode *deviceNode, struct ConfigDev *configDev ); +* amicall(ExpansionBase, 0x24, AddBootNode(d0,d1,a0,a1)) +* Comment out the line below to test AddDosNode/Enqueue stuff +* jsr AddBootNode(a6) move.l #1,PiSCSINextPart ; Switch to the next partition bra.w PartitionLoop @@ -338,16 +418,33 @@ FSResource: dc.l $0 LoadFileSystems: movem.l d0-d7/a0-a6,-(sp) ; Push registers to stack move.l #30,PiSCSIDebugMe + movea.l 4,a6 +ReloadResource: lea FileSysName(pc),a1 jsr OpenResource(a6) tst.l d0 bne FSRExists move.l #33,PiSCSIDebugMe ; FileSystem.resource isn't open, create it - lea FSRes(pc),a1 - move.l a1,-(a7) - jsr AddResource(a6) - move.l (a7)+,a0 + ; Code based on WinUAE filesys.asm + + moveq #32,d0 ; sizeof(FileSysResource) + move.l #$10001,d1 + jsr AllocMem(a6) + move.l d0,a2 + move.b #8,8(a2) ; NT_RESOURCE + lea FileSysName(pc),a0 + move.l a0,10(a2) ; node name + lea FileSysCreator(pc),a0 + move.l a0,14(a2) ; fsr_Creator + lea 18(a2),a0 + move.l a0,(a0) ; NewList() fsr_FileSysEntries + addq.l #4,(a0) + move.l a0,8(a0) + lea $150(a6),a0 ; ResourceList + move.l a2,a1 + jsr -$f6(a6) ; AddTail + move.l a2,a0 move.l a0,d0 FSRExists: @@ -357,7 +454,6 @@ FSRExists: move.l PiSCSIGetFS,d0 cmp.l #0,d0 beq.w FSDone - move.l d0,d7 FSNext: move.l #45,PiSCSIDebugMe @@ -384,18 +480,19 @@ NoEntries: move.l #39,PiSCSIDebugMe move.l PiSCSIFSSize,d0 move.l #40,PiSCSIDebugMe - move.l #0,d1 + move.l #$10001,d1 move.l #41,PiSCSIDebugMe jsr AllocMem(a6) move.l d0,PiSCSIAddr3 + move.l d0,a0 move.l #1,PiSCSICopyFS + move.b #NT_RESOURCE,LN_TYPE(a0) AlreadyLoaded: move.l #480,PiSCSIDebugMe move.l PiSCSIAddr2,a0 move.l #1,PiSCSINextFS move.l PiSCSIGetFS,d0 - move.l d0,d7 cmp.l #0,d0 bne.w FSNext @@ -403,9 +500,9 @@ FSDone: move.l #37,PiSCSIDebugMe move.l #32,PiSCSIDebugMe ; Couldn't open FileSystem.resource, Kick 1.2/1.3? movem.l (sp)+,d0-d7/a0-a6 ; Pop registers from stack - bra.w FSLoadExit + rts -FileSysRes +FSRes dc.l 0 dc.l 0 dc.b NT_RESOURCE diff --git a/platforms/amiga/piscsi/device_driver_amiga/build.sh b/platforms/amiga/piscsi/device_driver_amiga/build.sh index 3497372..5d94cb0 100644 --- a/platforms/amiga/piscsi/device_driver_amiga/build.sh +++ b/platforms/amiga/piscsi/device_driver_amiga/build.sh @@ -1,8 +1 @@ -#m68k-amigaos-gcc piscsi-amiga.c -ramiga-dev -noixemul -fbaserel -O2 -o pi-scsi.device -ldebug -lamiga -m68020 -#m68k-amigaos-gcc -m68020 -O2 -o pi-scsi.device -ramiga-dev -noixemul -fbaserel piscsi-amiga.c -ldebug -lamiga -#m68k-amigaos-gcc -m68020 -O2 -o scsi.device -ramiga-dev -noixemul -fbaserel piscsi-amiga.c -ldebug -lamiga -D_FSCSIDEV -#m68k-amigaos-gcc -m68020 -O2 -o 2nd.scsi.device -ramiga-dev -noixemul -fbaserel piscsi-amiga.c -ldebug -lamiga -D_FSCSI2ND vasmm68k_mot.exe -m68000 -Fhunk -I$VBCC/NDK39/include/include_i bootrom.s -o bootrom -#m68k-amigaos-as -m68020 bootrom.s && -#m68k-amigaos-objcopy --strip-all ./a.out ./bootrom -#rm ./a.out diff --git a/platforms/amiga/piscsi/device_driver_amiga/pi-scsi.device b/platforms/amiga/piscsi/device_driver_amiga/pi-scsi.device index 05c9b31..eaac293 100644 Binary files a/platforms/amiga/piscsi/device_driver_amiga/pi-scsi.device and b/platforms/amiga/piscsi/device_driver_amiga/pi-scsi.device differ diff --git a/platforms/amiga/piscsi/device_driver_amiga/piscsi-amiga-2.c b/platforms/amiga/piscsi/device_driver_amiga/piscsi-amiga-2.c index 8f4aba0..a325acb 100644 --- a/platforms/amiga/piscsi/device_driver_amiga/piscsi-amiga-2.c +++ b/platforms/amiga/piscsi/device_driver_amiga/piscsi-amiga-2.c @@ -68,11 +68,6 @@ uint8_t is_open; #define READSHORT(cmd, var) var = *(volatile unsigned short *)(PISCSI_OFFSET + cmd); #define READLONG(cmd, var) var = *(volatile unsigned long *)(PISCSI_OFFSET + cmd); -int __attribute__((no_reorder)) _start() -{ - return -1; -} - asm("romtag: \n" " dc.w "XSTR(RTC_MATCHWORD)" \n" " dc.l romtag \n" @@ -86,6 +81,11 @@ asm("romtag: \n" " dc.l _auto_init_tables \n" "endcode: \n"); +int __attribute__((no_reorder)) _start() +{ + return -1; +} + char device_name[] = DEVICE_NAME; char device_id_string[] = DEVICE_ID_STRING; @@ -219,14 +219,18 @@ uint8_t piscsi_rw(struct piscsi_unit *u, struct IORequest *io) { uint32_t len; //uint32_t block, num_blocks; uint8_t sderr = 0; + uint32_t block_size = 512; data = iotd->iotd_Req.io_Data; len = iotd->iotd_Req.io_Length; + WRITESHORT(PISCSI_CMD_DRVNUMX, u->unit_num); + READLONG(PISCSI_CMD_BLOCKSIZE, block_size); + if (data == 0) { return IOERR_BADADDRESS; } - if (len < PISCSI_BLOCK_SIZE) { + if (len < block_size) { iostd->io_Actual = 0; return IOERR_BADLENGTH; } @@ -252,16 +256,16 @@ uint8_t piscsi_rw(struct piscsi_unit *u, struct IORequest *io) { break; case TD_FORMAT: case CMD_WRITE: - WRITELONG(PISCSI_CMD_ADDR1, (iostd->io_Offset >> 9)); + WRITELONG(PISCSI_CMD_ADDR1, iostd->io_Offset); WRITELONG(PISCSI_CMD_ADDR2, len); WRITELONG(PISCSI_CMD_ADDR3, (uint32_t)data); - WRITESHORT(PISCSI_CMD_WRITE, u->unit_num); + WRITESHORT(PISCSI_CMD_WRITEBYTES, u->unit_num); break; case CMD_READ: - WRITELONG(PISCSI_CMD_ADDR1, (iostd->io_Offset >> 9)); + WRITELONG(PISCSI_CMD_ADDR1, iostd->io_Offset); WRITELONG(PISCSI_CMD_ADDR2, len); WRITELONG(PISCSI_CMD_ADDR3, (uint32_t)data); - WRITESHORT(PISCSI_CMD_READ, u->unit_num); + WRITESHORT(PISCSI_CMD_READBYTES, u->unit_num); break; } @@ -302,6 +306,10 @@ uint8_t piscsi_scsi(struct piscsi_unit *u, struct IORequest *io) uint32_t i, block = 0, blocks = 0, maxblocks = 0; uint8_t err = 0; uint8_t write = 0; + uint32_t block_size = 512; + + WRITESHORT(PISCSI_CMD_DRVNUMX, u->unit_num); + READLONG(PISCSI_CMD_BLOCKSIZE, block_size); debugval(PISCSI_DBG_VAL1, iostd->io_Length); debugval(PISCSI_DBG_VAL2, scsi->scsi_Command[0]); @@ -430,7 +438,7 @@ scsireadwrite:; WRITESHORT(PISCSI_CMD_DRVNUM, (u->scsi_num)); READLONG(PISCSI_CMD_BLOCKS, blocks); ((uint32_t*)data)[0] = blocks - 1; - ((uint32_t*)data)[1] = PISCSI_BLOCK_SIZE; + ((uint32_t*)data)[1] = block_size; scsi->scsi_Actual = 8; err = 0; @@ -450,7 +458,7 @@ scsireadwrite:; (blocks = (maxblocks - 1) & 0xFFFFFF); *((uint32_t *)&data[4]) = blocks; - *((uint32_t *)&data[8]) = PISCSI_BLOCK_SIZE; + *((uint32_t *)&data[8]) = block_size; switch (((UWORD)scsi->scsi_Command[2] << 8) | scsi->scsi_Command[3]) { case 0x0300: { // Format Device Mode @@ -463,7 +471,7 @@ scsireadwrite:; *((uint32_t *)&datext[4]) = 0; *((uint32_t *)&datext[8]) = 0; *((uint16_t *)&datext[10]) = u->s; - *((uint16_t *)&datext[12]) = PISCSI_BLOCK_SIZE; + *((uint16_t *)&datext[12]) = block_size; datext[14] = 0x00; datext[15] = 0x01; *((uint32_t *)&datext[16]) = 0; diff --git a/platforms/amiga/piscsi/device_driver_amiga/piscsi-amiga.c b/platforms/amiga/piscsi/device_driver_amiga/piscsi-amiga.c deleted file mode 100644 index d8a6dc0..0000000 --- a/platforms/amiga/piscsi/device_driver_amiga/piscsi-amiga.c +++ /dev/null @@ -1,621 +0,0 @@ -// SPDX-License-Identifier: MIT - -/* - * Based on: - * Amiga ZZ9000 USB Storage Driver (ZZ9000USBStorage.device) - * Copyright (C) 2016-2020, Lukas F. Hartmann - * Based on code Copyright (C) 2016, Jason S. McMullan - * All rights reserved. - * - * Licensed under the MIT License: - * - * 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 -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include "../piscsi-enums.h" - -#define WRITESHORT(cmd, val) *(unsigned short *)((unsigned long)(PISCSI_OFFSET+cmd)) = val; -#define WRITELONG(cmd, val) *(unsigned long *)((unsigned long)(PISCSI_OFFSET+cmd)) = val; -#define WRITEBYTE(cmd, val) *(unsigned char *)((unsigned long)(PISCSI_OFFSET+cmd)) = val; - -#define READSHORT(cmd, var) var = *(volatile unsigned short *)(PISCSI_OFFSET + cmd); -#define READLONG(cmd, var) var = *(volatile unsigned long *)(PISCSI_OFFSET + cmd); - -#pragma pack(4) -struct piscsi_base { - struct Device* pi_dev; - struct piscsi_unit { - struct Unit unit; - uint32_t regs_ptr; - - uint8_t enabled; - uint8_t present; - uint8_t valid; - uint8_t read_only; - uint8_t motor; - uint8_t unit_num; - uint16_t h, s; - uint32_t c; - - uint32_t change_num; - } units[NUM_UNITS]; -}; - -struct ExecBase* SysBase = NULL; - -#ifdef _FSCSIDEV -const char DevName[] = "scsi.device"; -#elif _FSCSI2ND -const char DevName[] = "2nd.scsi.device"; -#else -const char DevName[] = "pi-scsi.device"; -#endif -const char DevIdString[] = "Pi-SCSI 0.1"; - -const UWORD DevVersion = 43; -const UWORD DevRevision = 10; - -#include "stabs.h" - -struct piscsi_base *dev_base = NULL; - -struct WBStartup *_WBenchMsg = NULL; - -//#define exit(...) -//#define debug(...) -#define KPrintF(...) -#define debug(...) -#define debugval(...) -//#define debug(c, v) WRITESHORT(c, v) -//#define debugval(c, v) WRITELONG(c, v) - -//#define bug(x,args...) KPrintF(x ,##args); -//#define debug(x,args...) bug("%s:%ld " x "\n", __func__, (unsigned long)__LINE__ ,##args) - -uint8_t piscsi_perform_io(struct piscsi_unit *u, struct IORequest *io); -uint8_t piscsi_rw(struct piscsi_unit *u, struct IORequest *io, uint32_t offset, uint8_t write); -uint8_t piscsi_scsi(struct piscsi_unit *u, struct IORequest *io); - -extern void* DOSBase[2]; - -uint32_t __UserDevInit(struct Device* dev) { - //uint8_t* registers = NULL; - SysBase = *(struct ExecBase **)4L; - - KPrintF("Initializing devices.\n"); - debug(PISCSI_DBG_MSG, DBG_INIT); - - dev_base = AllocMem(sizeof(struct piscsi_base), MEMF_PUBLIC | MEMF_CLEAR); - dev_base->pi_dev = dev; - - for (int i = 0; i < NUM_UNITS; i++) { - uint16_t r = 0; - WRITESHORT(PISCSI_CMD_DRVNUM, i); - dev_base->units[i].regs_ptr = PISCSI_OFFSET; - READSHORT(PISCSI_CMD_DRVTYPE, r); - dev_base->units[i].enabled = r; - dev_base->units[i].present = r; - dev_base->units[i].valid = r; - dev_base->units[i].unit_num = i; - if (dev_base->units[i].present) { - READLONG(PISCSI_CMD_CYLS, dev_base->units[i].c); - READSHORT(PISCSI_CMD_HEADS, dev_base->units[i].h); - READSHORT(PISCSI_CMD_SECS, dev_base->units[i].s); - KPrintF("C/H/S: %ld / %ld / %ld\n", dev_base->units[i].c, dev_base->units[i].h, dev_base->units[i].s); - - debugval(PISCSI_DBG_VAL1, dev_base->units[i].c); - debugval(PISCSI_DBG_VAL2, dev_base->units[i].h); - debugval(PISCSI_DBG_VAL3, dev_base->units[i].s); - debug(PISCSI_DBG_MSG, DBG_CHS); - } - dev_base->units[i].change_num++; - // Send any reset signal to the "SCSI" device here. - } - - return 1; -} - -uint32_t __UserDevCleanup(void) { - KPrintF("Cleaning up.\n"); - debug(PISCSI_DBG_MSG, DBG_CLEANUP); - FreeMem(dev_base, sizeof(struct piscsi_base)); - return 0; -} - -uint32_t __UserDevOpen(struct IOExtTD *iotd, uint32_t num, uint32_t flags) { - struct Node* node = (struct Node*)iotd; - int io_err = IOERR_OPENFAIL; - - //WRITESHORT(PISCSI_CMD_DEBUGME, 1); - - int unit_num = 0; - WRITELONG(PISCSI_CMD_DRVNUM, num); - READLONG(PISCSI_CMD_DRVNUM, unit_num); - - debugval(PISCSI_DBG_VAL1, unit_num); - debugval(PISCSI_DBG_VAL2, flags); - debugval(PISCSI_DBG_VAL3, num); - debug(PISCSI_DBG_MSG, DBG_OPENDEV); - KPrintF("Opening device %ld Flags: %ld (%lx)\n", unit_num, flags, flags); - - if (iotd && unit_num < NUM_UNITS) { - if (dev_base->units[unit_num].enabled && dev_base->units[unit_num].present) { - io_err = 0; - iotd->iotd_Req.io_Unit = (struct Unit*)&dev_base->units[unit_num].unit; - iotd->iotd_Req.io_Unit->unit_flags = UNITF_ACTIVE; - iotd->iotd_Req.io_Unit->unit_OpenCnt = 1; - } - } - -skip:; - iotd->iotd_Req.io_Error = io_err; - - return io_err; -} - -uint32_t __UserDevClose(struct IOExtTD *iotd) { - return 0; -} - -void exit(int status) { } - -ADDTABL_1(__BeginIO,a1); -void __BeginIO(struct IORequest *io) { - if (dev_base == NULL || io == NULL) - return; - - struct piscsi_unit *u; - struct Node* node = (struct Node*)io; - u = (struct piscsi_unit *)io->io_Unit; - - if (node == NULL || u == NULL) - return; - - debugval(PISCSI_DBG_VAL1, io->io_Command); - debugval(PISCSI_DBG_VAL2, io->io_Flags); - debugval(PISCSI_DBG_VAL3, (io->io_Flags & IOF_QUICK)); - debug(PISCSI_DBG_MSG, DBG_BEGINIO); - KPrintF("io_Command = %ld, io_Flags = 0x%lx quick = %lx\n", io->io_Command, io->io_Flags, (io->io_Flags & IOF_QUICK)); - io->io_Error = piscsi_perform_io(u, io); - - if (!(io->io_Flags & IOF_QUICK)) { - ReplyMsg(&io->io_Message); - } -} - -ADDTABL_1(__AbortIO,a1); -void __AbortIO(struct IORequest* io) { - debug(PISCSI_DBG_MSG, DBG_ABORTIO); - KPrintF("AbortIO!\n"); - if (!io) return; - io->io_Error = IOERR_ABORTED; -} - -uint8_t piscsi_rw(struct piscsi_unit *u, struct IORequest *io, uint32_t offset, uint8_t write) { - struct IOStdReq *iostd = (struct IOStdReq *)io; - struct IOExtTD *iotd = (struct IOExtTD *)io; - - uint8_t* data; - uint32_t len, num_blocks; - uint32_t block, max_addr; - uint8_t sderr; - - data = iotd->iotd_Req.io_Data; - len = iotd->iotd_Req.io_Length; - //uint32_t offset2 = iostd->io_Offset; - - max_addr = 0xffffffff; - - // well... if we had 64 bits this would make sense - if ((offset > max_addr) || (offset+len > max_addr)) - return IOERR_BADADDRESS; - if (data == 0) - return IOERR_BADADDRESS; - if (len < PISCSI_BLOCK_SIZE) { - iostd->io_Actual = 0; - return IOERR_BADLENGTH; - } - - //block = offset;// >> SD_SECTOR_SHIFT; - //num_blocks = len;// >> SD_SECTOR_SHIFT; - sderr = 0; - - if (write) { - //uint32_t retries = 10; - //KPrintF("Write %lx -> %lx %lx\n", (uint32_t)data, offset, len); - WRITELONG(PISCSI_CMD_ADDR1, (offset >> 9)); - WRITELONG(PISCSI_CMD_ADDR2, len); - WRITELONG(PISCSI_CMD_ADDR3, (uint32_t)data); - WRITESHORT(PISCSI_CMD_WRITE, u->unit_num); - } else { - //KPrintF("read %lx %lx -> %lx\n", offset, len, (uint32_t)data); - WRITELONG(PISCSI_CMD_ADDR1, (offset >> 9)); - WRITELONG(PISCSI_CMD_ADDR2, len); - WRITELONG(PISCSI_CMD_ADDR3, (uint32_t)data); - WRITESHORT(PISCSI_CMD_READ, u->unit_num); - } - - if (sderr) { - iostd->io_Actual = 0; - - if (sderr & SCSIERR_TIMEOUT) - return TDERR_DiskChanged; - if (sderr & SCSIERR_PARAM) - return TDERR_SeekError; - if (sderr & SCSIERR_ADDRESS) - return TDERR_SeekError; - if (sderr & (SCSIERR_ERASESEQ | SCSIERR_ERASERES)) - return TDERR_BadSecPreamble; - if (sderr & SCSIERR_CRC) - return TDERR_BadSecSum; - if (sderr & SCSIERR_ILLEGAL) - return TDERR_TooFewSecs; - if (sderr & SCSIERR_IDLE) - return TDERR_PostReset; - - return TDERR_SeekError; - } else { - iostd->io_Actual = len; - } - - return 0; -} - -#define PISCSI_ID_STRING "PISTORM Fake SCSI Disk 0.1 1111111111111111" - -uint8_t piscsi_scsi(struct piscsi_unit *u, struct IORequest *io) -{ - struct IOStdReq *iostd = (struct IOStdReq *)io; - struct SCSICmd *scsi = iostd->io_Data; - //uint8_t* registers = sdu->sdu_Registers; - uint8_t *data = (uint8_t *)scsi->scsi_Data; - uint32_t i, block, blocks, maxblocks; - uint8_t err = 0; - - KPrintF("SCSI len=%ld, cmd = %02lx %02lx %02lx ... (%ld)\n", - iostd->io_Length, scsi->scsi_Command[0], - scsi->scsi_Command[1], scsi->scsi_Command[2], - scsi->scsi_CmdLength); - - debugval(PISCSI_DBG_VAL1, iostd->io_Length); - debugval(PISCSI_DBG_VAL2, scsi->scsi_Command[0]); - debugval(PISCSI_DBG_VAL3, scsi->scsi_Command[1]); - debugval(PISCSI_DBG_VAL4, scsi->scsi_Command[2]); - debugval(PISCSI_DBG_VAL5, scsi->scsi_CmdLength); - debug(PISCSI_DBG_MSG, DBG_SCSICMD); - - //maxblocks = u->s * u->c * u->h; - - if (scsi->scsi_CmdLength < 6) { - //KPrintF("SCSICMD BADLENGTH2"); - return IOERR_BADLENGTH; - } - - if (scsi->scsi_Command == NULL) { - //KPrintF("SCSICMD IOERR_BADADDRESS1"); - return IOERR_BADADDRESS; - } - - scsi->scsi_Actual = 0; - //iostd->io_Actual = sizeof(*scsi); - - switch (scsi->scsi_Command[0]) { - case 0x00: // TEST_UNIT_READY - err = 0; - break; - - case 0x12: // INQUIRY - for (i = 0; i < scsi->scsi_Length; i++) { - uint8_t val = 0; - - switch (i) { - case 0: // SCSI device type: direct-access device - val = (0 << 5) | 0; - break; - case 1: // RMB = 1 - val = (1 << 7); - break; - case 2: // VERSION = 0 - val = 0; - break; - case 3: // NORMACA=0, HISUP = 0, RESPONSE_DATA_FORMAT = 2 - val = (0 << 5) | (0 << 4) | 2; - break; - case 4: // ADDITIONAL_LENGTH = 44 - 4 - val = 44 - 4; - break; - default: - if (i >= 8 && i < 44) - val = PISCSI_ID_STRING[i - 8]; - else - val = 0; - break; - } - data[i] = val; - } - scsi->scsi_Actual = i; - err = 0; - break; - - case 0x08: // READ (6) - case 0x0a: // WRITE (6) - block = scsi->scsi_Command[1] & 0x1f; - block = (block << 8) | scsi->scsi_Command[2]; - block = (block << 8) | scsi->scsi_Command[3]; - blocks = scsi->scsi_Command[4]; - - READLONG(PISCSI_CMD_BLOCKS, maxblocks); - if (block + blocks > maxblocks) { - err = IOERR_BADADDRESS; - break; - } - /*if (scsi->scsi_Length < (blocks << SD_SECTOR_SHIFT)) { - err = IOERR_BADLENGTH; - break; - }*/ - if (data == NULL) { - err = IOERR_BADADDRESS; - break; - } - - if (scsi->scsi_Command[0] == 0x08) { - //KPrintF("scsi_read %lx %lx\n",block,blocks); - //KPrintF("SCSI read %lx %lx -> %lx\n", block, blocks, (uint32_t)data); - WRITELONG(PISCSI_CMD_ADDR2, block); - WRITELONG(PISCSI_CMD_ADDR2, (blocks << 9)); - WRITELONG(PISCSI_CMD_ADDR3, (uint32_t)data); - WRITESHORT(PISCSI_CMD_READ, u->unit_num); - } - else { - //KPrintF("scsi_write %lx %lx\n",block,blocks); - //KPrintF("SCSI write %lx -> %lx %lx\n", (uint32_t)data, block, blocks); - WRITELONG(PISCSI_CMD_ADDR2, block); - WRITELONG(PISCSI_CMD_ADDR2, (blocks << 9)); - WRITELONG(PISCSI_CMD_ADDR3, (uint32_t)data); - WRITESHORT(PISCSI_CMD_WRITE, u->unit_num); - } - - scsi->scsi_Actual = scsi->scsi_Length; - err = 0; - break; - - case 0x25: // READ CAPACITY (10) - //KPrintF("SCSI command: Read Capacity.\n"); - if (scsi->scsi_CmdLength < 10) { - err = HFERR_BadStatus; - break; - } - - block = *((uint32_t*)&scsi->scsi_Command[2]); - - /*if ((scsi->scsi_Command[8] & 1) || block != 0) { - // PMI Not supported - KPrintF("PMI not supported.\n"); - err = HFERR_BadStatus; - break; - }*/ - - if (scsi->scsi_Length < 8) { - err = IOERR_BADLENGTH; - break; - } - - READLONG(PISCSI_CMD_BLOCKS, blocks); - ((uint32_t*)data)[0] = blocks - 1; - ((uint32_t*)data)[1] = PISCSI_BLOCK_SIZE; - - scsi->scsi_Actual = 8; - err = 0; - - break; - case 0x1a: // MODE SENSE (6) - //KPrintF("SCSI command: Mode Sense.\n"); - data[0] = 3 + 8 + 0x16; - data[1] = 0; // MEDIUM TYPE - data[2] = 0; - data[3] = 8; - - READLONG(PISCSI_CMD_BLOCKS, maxblocks); - (blocks = (maxblocks - 1) & 0xFFFFFF); - - *((uint32_t *)&data[4]) = blocks; - *((uint32_t *)&data[8]) = PISCSI_BLOCK_SIZE; - - switch (((UWORD)scsi->scsi_Command[2] << 8) | scsi->scsi_Command[3]) { - case 0x0300: { // Format Device Mode - KPrintF("Grabbing SCSI format device mode data.\n"); - debug(PISCSI_DBG_MSG, DBG_SCSI_FORMATDEVICE); - uint8_t *datext = data + 12; - datext[0] = 0x03; - datext[1] = 0x16; - datext[2] = 0x00; - datext[3] = 0x01; - *((uint32_t *)&datext[4]) = 0; - *((uint32_t *)&datext[8]) = 0; - *((uint16_t *)&datext[10]) = u->s; - *((uint16_t *)&datext[12]) = PISCSI_BLOCK_SIZE; - datext[14] = 0x00; - datext[15] = 0x01; - *((uint32_t *)&datext[16]) = 0; - datext[20] = 0x80; - - scsi->scsi_Actual = data[0] + 1; - err = 0; - break; - } - case 0x0400: // Rigid Drive Geometry - KPrintF("Grabbing SCSI rigid drive geometry.\n"); - debug(PISCSI_DBG_MSG, DBG_SCSI_RDG); - uint8_t *datext = data + 12; - datext[0] = 0x04; - *((uint32_t *)&datext[1]) = u->c; - datext[1] = 0x16; - datext[5] = u->h; - datext[6] = 0x00; - *((uint32_t *)&datext[6]) = 0; - *((uint32_t *)&datext[10]) = 0; - *((uint32_t *)&datext[13]) = u->c; - datext[17] = 0; - *((uint32_t *)&datext[18]) = 0; - *((uint16_t *)&datext[20]) = 5400; - - scsi->scsi_Actual = data[0] + 1; - err = 0; - break; - - default: - KPrintF("[WARN] Unhandled mode sense thing: %lx\n", ((UWORD)scsi->scsi_Command[2] << 8) | scsi->scsi_Command[3]); - debugval(PISCSI_DBG_VAL1, (((UWORD)scsi->scsi_Command[2] << 8) | scsi->scsi_Command[3])); - debug(PISCSI_DBG_MSG, DBG_SCSI_UNKNOWN_MODESENSE); - err = HFERR_BadStatus; - break; - } - break; - - case 0x37: // READ DEFECT DATA (10) - - break; - - default: - debugval(PISCSI_DBG_VAL1, scsi->scsi_Command[0]); - debug(PISCSI_DBG_MSG, DBG_SCSI_UNKNOWN_COMMAND); - KPrintF("Unknown/unhandled SCSI command %lx.\n", scsi->scsi_Command[0]); - err = HFERR_BadStatus; - break; - } - - if (err != 0) { - debugval(PISCSI_DBG_VAL1, err); - debug(PISCSI_DBG_MSG, DBG_SCSIERR); - KPrintF("Some SCSI error occured: %ld\n", err); - scsi->scsi_Actual = 0; - } - - return err; -} - -#define DUMMYCMD iostd->io_Actual = 0; break; -uint8_t piscsi_perform_io(struct piscsi_unit *u, struct IORequest *io) { - struct IOStdReq *iostd = (struct IOStdReq *)io; - struct IOExtTD *iotd = (struct IOExtTD *)io; - - uint8_t *data; - uint32_t len; - uint32_t offset; - //struct DriveGeometry *geom; - uint8_t err = 0; - - if (!u->enabled) { - return IOERR_OPENFAIL; - } - - data = iotd->iotd_Req.io_Data; - len = iotd->iotd_Req.io_Length; - - if (io->io_Error == IOERR_ABORTED) { - return io->io_Error; - } - - debugval(PISCSI_DBG_VAL1, io->io_Command); - debugval(PISCSI_DBG_VAL2, io->io_Flags); - debugval(PISCSI_DBG_VAL3, iostd->io_Length); - debug(PISCSI_DBG_MSG, DBG_IOCMD); - //KPrintF("cmd: %s\n",cmd_name(io->io_Command)); - //KPrintF("IO %lx Start, io_Flags = %ld, io_Command = %ld\n", io, io->io_Flags, io->io_Command); - - switch (io->io_Command) { - case CMD_CLEAR: - /* Invalidate read buffer */ - DUMMYCMD; - case CMD_UPDATE: - /* Flush write buffer */ - DUMMYCMD; - case TD_PROTSTATUS: - DUMMYCMD; - case TD_CHANGENUM: - iostd->io_Actual = u->change_num; - break; - case TD_REMOVE: - DUMMYCMD; - case TD_CHANGESTATE: - DUMMYCMD; - case TD_GETDRIVETYPE: - iostd->io_Actual = DG_DIRECT_ACCESS; - break; - case TD_MOTOR: - iostd->io_Actual = u->motor; - u->motor = iostd->io_Length ? 1 : 0; - break; - - case TD_FORMAT: - offset = iotd->iotd_Req.io_Offset; - //err = 0; - err = piscsi_rw(u, io, offset, 1); - break; - case CMD_WRITE: - offset = iotd->iotd_Req.io_Offset; - //err = 0; - err = piscsi_rw(u, io, offset, 1); - break; - case CMD_READ: - offset = iotd->iotd_Req.io_Offset; - //err = 0; - err = piscsi_rw(u, io, offset, 0); - break; - case HD_SCSICMD: - //err = 0; - err = piscsi_scsi(u, io); - break; - default: { - int cmd = io->io_Command; - debug(PISCSI_DBG_MSG, DBG_IOCMD_UNHANDLED); - err = IOERR_NOCMD; - break; - } - } - - return err; -} -#undef DUMMYCMD - -ADDTABL_END(); diff --git a/platforms/amiga/piscsi/piscsi-enums.h b/platforms/amiga/piscsi/piscsi-enums.h index 9cffcdd..1040822 100644 --- a/platforms/amiga/piscsi/piscsi-enums.h +++ b/platforms/amiga/piscsi/piscsi-enums.h @@ -15,45 +15,52 @@ #define SCSIERR_IDLE (1 << 0) enum piscsi_stuff { - PISCSI_BLOCK_SIZE = 512, + PISCSI_BLOCK_SIZE = 512, // Deprecated, do not use + PISCSI_MAX_BLOCK_SIZE = 65536, PISCSI_TRACK_SECTORS = 2048, }; enum piscsi_cmds { - PISCSI_CMD_WRITE = 0x00, - PISCSI_CMD_READ = 0x02, - PISCSI_CMD_DRVNUM = 0x04, - PISCSI_CMD_DRVTYPE = 0x06, - PISCSI_CMD_BLOCKS = 0x08, - PISCSI_CMD_CYLS = 0x0A, - PISCSI_CMD_HEADS = 0x0C, - PISCSI_CMD_SECS = 0x0E, - PISCSI_CMD_ADDR1 = 0x10, - PISCSI_CMD_ADDR2 = 0x14, - PISCSI_CMD_ADDR3 = 0x18, - PISCSI_CMD_ADDR4 = 0x1C, - PISCSI_CMD_DEBUGME = 0x20, - PISCSI_CMD_DRIVER = 0x40, - PISCSI_CMD_NEXTPART = 0x44, - PISCSI_CMD_GETPART = 0x48, - PISCSI_CMD_GETPRIO = 0x4C, - PISCSI_CMD_WRITE64 = 0x50, - PISCSI_CMD_READ64 = 0x52, - PISCSI_CMD_CHECKFS = 0x60, - PISCSI_CMD_NEXTFS = 0x64, - PISCSI_CMD_COPYFS = 0x68, - PISCSI_CMD_FSSIZE = 0x6C, - PISCSI_CMD_SETFSH = 0x70, - PISCSI_DBG_MSG = 0x1000, - PISCSI_DBG_VAL1 = 0x1010, - PISCSI_DBG_VAL2 = 0x1014, - PISCSI_DBG_VAL3 = 0x1018, - PISCSI_DBG_VAL4 = 0x101C, - PISCSI_DBG_VAL5 = 0x1020, - PISCSI_DBG_VAL6 = 0x1024, - PISCSI_DBG_VAL7 = 0x1028, - PISCSI_DBG_VAL8 = 0x102C, - PISCSI_CMD_ROM = 0x4000, + PISCSI_CMD_WRITE = 0x00, + PISCSI_CMD_READ = 0x02, + PISCSI_CMD_DRVNUM = 0x04, + PISCSI_CMD_DRVTYPE = 0x06, + PISCSI_CMD_BLOCKS = 0x08, + PISCSI_CMD_CYLS = 0x0A, + PISCSI_CMD_HEADS = 0x0C, + PISCSI_CMD_SECS = 0x0E, + PISCSI_CMD_ADDR1 = 0x10, + PISCSI_CMD_ADDR2 = 0x14, + PISCSI_CMD_ADDR3 = 0x18, + PISCSI_CMD_ADDR4 = 0x1C, + PISCSI_CMD_DEBUGME = 0x20, + PISCSI_CMD_DRIVER = 0x40, + PISCSI_CMD_NEXTPART = 0x44, + PISCSI_CMD_GETPART = 0x48, + PISCSI_CMD_GETPRIO = 0x4C, + PISCSI_CMD_WRITE64 = 0x50, + PISCSI_CMD_READ64 = 0x52, + PISCSI_CMD_CHECKFS = 0x60, + PISCSI_CMD_NEXTFS = 0x64, + PISCSI_CMD_COPYFS = 0x68, + PISCSI_CMD_FSSIZE = 0x6C, + PISCSI_CMD_SETFSH = 0x70, + PISCSI_CMD_BLOCKSIZE = 0x74, + PISCSI_CMD_READBYTES = 0x78, + PISCSI_CMD_WRITEBYTES = 0x7C, + PISCSI_CMD_DRVNUMX = 0x80, + PISCSI_CMD_LOADFS = 0x84, + PISCSI_CMD_GET_FS_INFO = 0x88, + PISCSI_DBG_MSG = 0x1000, + PISCSI_DBG_VAL1 = 0x1010, + PISCSI_DBG_VAL2 = 0x1014, + PISCSI_DBG_VAL3 = 0x1018, + PISCSI_DBG_VAL4 = 0x101C, + PISCSI_DBG_VAL5 = 0x1020, + PISCSI_DBG_VAL6 = 0x1024, + PISCSI_DBG_VAL7 = 0x1028, + PISCSI_DBG_VAL8 = 0x102C, + PISCSI_CMD_ROM = 0x4000, }; enum piscsi_dbg_msgs { diff --git a/platforms/amiga/piscsi/piscsi.c b/platforms/amiga/piscsi/piscsi.c index 5198341..263fb93 100644 --- a/platforms/amiga/piscsi/piscsi.c +++ b/platforms/amiga/piscsi/piscsi.c @@ -23,9 +23,10 @@ #ifdef PISCSI_DEBUG #define DEBUG printf //#define DEBUG_TRIVIAL printf -#define DEBUG_TRVIAL(...) +#define DEBUG_TRIVIAL(...) -extern void stop_cpu_emulation(uint8_t disasm_cur); +//extern void stop_cpu_emulation(uint8_t disasm_cur); +#define stop_cpu_emulation(...) static const char *op_type_names[4] = { "BYTE", @@ -75,27 +76,59 @@ void piscsi_init() { devs[i].c = devs[i].h = devs[i].s = 0; } - FILE *in = fopen("./platforms/amiga/piscsi/piscsi.rom", "rb"); - if (in == NULL) { - printf("[PISCSI] Could not open PISCSI Boot ROM file for reading!\n"); - // Zero out the boot ROM offset from the autoconfig ROM. - ac_piscsi_rom[20] = 0; - ac_piscsi_rom[21] = 0; - ac_piscsi_rom[22] = 0; - ac_piscsi_rom[23] = 0; - return; + if (piscsi_rom_ptr == NULL) { + FILE *in = fopen("./platforms/amiga/piscsi/piscsi.rom", "rb"); + if (in == NULL) { + printf("[PISCSI] Could not open PISCSI Boot ROM file for reading!\n"); + // Zero out the boot ROM offset from the autoconfig ROM. + ac_piscsi_rom[20] = 0; + ac_piscsi_rom[21] = 0; + ac_piscsi_rom[22] = 0; + ac_piscsi_rom[23] = 0; + return; + } + fseek(in, 0, SEEK_END); + piscsi_rom_size = ftell(in); + fseek(in, 0, SEEK_SET); + piscsi_rom_ptr = malloc(piscsi_rom_size); + fread(piscsi_rom_ptr, piscsi_rom_size, 1, in); + + fseek(in, PISCSI_DRIVER_OFFSET, SEEK_SET); + process_hunks(in, &piscsi_hinfo, piscsi_hreloc, PISCSI_DRIVER_OFFSET); + + fclose(in); + + printf("[PISCSI] Loaded Boot ROM.\n"); + } else { + printf("[PISCSI] Boot ROM already loaded.\n"); } - fseek(in, 0, SEEK_END); - piscsi_rom_size = ftell(in); - fseek(in, 0, SEEK_SET); - piscsi_rom_ptr = malloc(piscsi_rom_size); - fread(piscsi_rom_ptr, piscsi_rom_size, 1, in); + fflush(stdout); +} - fseek(in, PISCSI_DRIVER_OFFSET, SEEK_SET); - process_hunks(in, &piscsi_hinfo, piscsi_hreloc, PISCSI_DRIVER_OFFSET); +void piscsi_shutdown() { + printf("[PISCSI] Shutting down PiSCSI.\n"); + for (int i = 0; i < 8; i++) { + if (devs[i].fd != -1) { + close(devs[i].fd); + devs[i].fd = -1; + devs[i].block_size = 0; + } + } - fclose(in); - printf("[PISCSI] Loaded Boot ROM.\n"); + for (int i = 0; i < NUM_FILESYSTEMS; i++) { + if (filesystems[i].binary_data) { + free(filesystems[i].binary_data); + filesystems[i].binary_data = NULL; + } + if (filesystems[i].fhb) { + free(filesystems[i].fhb); + filesystems[i].fhb = NULL; + } + filesystems[i].h_info.current_hunk = 0; + filesystems[i].h_info.reloc_hunks = 0; + filesystems[i].FS_ID = 0; + filesystems[i].handler = 0; + } } void piscsi_find_partitions(struct piscsi_dev *d) { @@ -115,11 +148,11 @@ void piscsi_find_partitions(struct piscsi_dev *d) { return; } - char *block = malloc(512); + char *block = malloc(d->block_size); - lseek(fd, BE(d->rdb->rdb_PartitionList) * 512, SEEK_SET); + lseek(fd, BE(d->rdb->rdb_PartitionList) * d->block_size, SEEK_SET); next_partition:; - read(fd, block, 512); + read(fd, block, d->block_size); uint32_t first = be32toh(*((uint32_t *)&block[0])); if (first != PART_IDENTIFIER) { @@ -137,8 +170,8 @@ next_partition:; if (d->pb[cur_partition]->pb_Next != 0xFFFFFFFF) { uint64_t next = be32toh(pb->pb_Next); - block = malloc(512); - lseek64(fd, next * 512, SEEK_SET); + block = malloc(d->block_size); + lseek64(fd, next * d->block_size, SEEK_SET); cur_partition++; DEBUG("[PISCSI] Next partition at block %d.\n", be32toh(pb->pb_Next)); goto next_partition; @@ -153,11 +186,11 @@ next_partition:; int piscsi_parse_rdb(struct piscsi_dev *d) { int fd = d->fd; int i = 0; - uint8_t *block = malloc(512); + uint8_t *block = malloc(PISCSI_MAX_BLOCK_SIZE); lseek(fd, 0, SEEK_SET); for (i = 0; i < RDB_BLOCK_LIMIT; i++) { - read(fd, block, 512); + read(fd, block, PISCSI_MAX_BLOCK_SIZE); uint32_t first = be32toh(*((uint32_t *)&block[0])); if (first == RDB_IDENTIFIER) goto rdb_found; @@ -171,6 +204,8 @@ rdb_found:; d->s = be32toh(rdb->rdb_Sectors); d->num_partitions = 0; DEBUG("[PISCSI] RDB - first partition at block %d.\n", be32toh(rdb->rdb_PartitionList)); + d->block_size = be32toh(rdb->rdb_BlockBytes); + DEBUG("[PISCSI] Block size: %d. (%d)\n", be32toh(rdb->rdb_BlockBytes), d->block_size); if (d->rdb) free(d->rdb); d->rdb = rdb; @@ -219,12 +254,12 @@ void piscsi_find_filesystems(struct piscsi_dev *d) { uint8_t fs_found = 0; - uint8_t *fhb_block = malloc(512); + uint8_t *fhb_block = malloc(d->block_size); lseek64(d->fd, d->fshd_offs, SEEK_SET); struct FileSysHeaderBlock *fhb = (struct FileSysHeaderBlock *)fhb_block; - read(d->fd, fhb_block, 512); + read(d->fd, fhb_block, d->block_size); while (BE(fhb->fhb_ID) == FS_IDENTIFIER) { char *dosID = (char *)&fhb->fhb_DosType; @@ -238,7 +273,7 @@ void piscsi_find_filesystems(struct piscsi_dev *d) { DEBUG("[FSHD] Patchflags: %d Type: %d\n", BE(fhb->fhb_PatchFlags), BE(fhb->fhb_Type)); DEBUG("[FSHD] Task: %d Lock: %d\n", BE(fhb->fhb_Task), BE(fhb->fhb_Lock)); DEBUG("[FSHD] Handler: %d StackSize: %d\n", BE(fhb->fhb_Handler), BE(fhb->fhb_StackSize)); - DEBUG("[FSHD] Prio: %d Startup: %d\n", BE(fhb->fhb_Priority), BE(fhb->fhb_Startup)); + DEBUG("[FSHD] Prio: %d Startup: %d (%.8X)\n", BE(fhb->fhb_Priority), BE(fhb->fhb_Startup), BE(fhb->fhb_Startup)); DEBUG("[FSHD] SegListBlocks: %d GlobalVec: %d\n", BE(fhb->fhb_Priority), BE(fhb->fhb_Startup)); DEBUG("[FSHD] FileSysName: %s\n", fhb->fhb_FileSysName + 1); #endif @@ -253,19 +288,37 @@ void piscsi_find_filesystems(struct piscsi_dev *d) { } } - if (load_lseg(d->fd, &filesystems[piscsi_num_fs].binary_data, &filesystems[piscsi_num_fs].h_info, filesystems[piscsi_num_fs].relocs) != -1) { + if (load_lseg(d->fd, &filesystems[piscsi_num_fs].binary_data, &filesystems[piscsi_num_fs].h_info, filesystems[piscsi_num_fs].relocs, d->block_size) != -1) { filesystems[piscsi_num_fs].FS_ID = fhb->fhb_DosType; filesystems[piscsi_num_fs].fhb = fhb; printf("[FSHD] Loaded and set up file system %d: %c%c%c/%d\n", piscsi_num_fs + 1, dosID[0], dosID[1], dosID[2], dosID[3]); + { + char fs_save_filename[256]; + memset(fs_save_filename, 0x00, 256); + sprintf(fs_save_filename, "./data/fs/%c%c%c.%d", dosID[0], dosID[1], dosID[2], dosID[3]); + FILE *save_fs = fopen(fs_save_filename, "rb"); + if (save_fs == NULL) { + save_fs = fopen(fs_save_filename, "wb+"); + if (save_fs != NULL) { + fwrite(filesystems[piscsi_num_fs].binary_data, filesystems[piscsi_num_fs].h_info.byte_size, 1, save_fs); + fclose(save_fs); + printf("[FSHD] File system %c%c%c/%d saved to fs storage.\n", dosID[0], dosID[1], dosID[2], dosID[3]); + } else { + printf("[FSHD] Failed to save file system to fs storage. (Permission issues?)\n"); + } + } else { + fclose(save_fs); + } + } piscsi_num_fs++; } skip_fs_load_lseg:; fs_found++; - lseek64(d->fd, BE(fhb->fhb_Next) * 512, SEEK_SET); - fhb_block = malloc(512); + lseek64(d->fd, BE(fhb->fhb_Next) * d->block_size, SEEK_SET); + fhb_block = malloc(d->block_size); fhb = (struct FileSysHeaderBlock *)fhb_block; - read(d->fd, fhb_block, 512); + read(d->fd, fhb_block, d->block_size); } if (!fs_found) { @@ -277,6 +330,10 @@ fs_done:; free(fhb_block); } +struct piscsi_dev *piscsi_get_dev(uint8_t index) { + return &devs[index]; +} + void piscsi_map_drive(char *filename, uint8_t index) { if (index > 7) { printf("[PISCSI] Drive index %d out of range.\nUnable to map file %s to drive.\n", index, filename); @@ -302,11 +359,15 @@ void piscsi_map_drive(char *filename, uint8_t index) { d->h = 16; d->s = 63; d->c = (file_size / 512) / (d->s * d->h); + d->block_size = 512; } printf("[PISCSI] CHS: %d %d %d\n", d->c, d->h, d->s); + printf ("Finding partitions.\n"); piscsi_find_partitions(d); + printf ("Finding file systems.\n"); piscsi_find_filesystems(d); + printf ("Done.\n"); } void piscsi_unmap_drive(uint8_t index) { @@ -479,6 +540,7 @@ void print_piscsi_debug_message(int index) { void piscsi_debugme(uint32_t index) { switch (index) { DEBUGME_SIMPLE(1, "[PISCSI-DEBUGME] Arrived at DiagEntry.\n"); + DEBUGME_SIMPLE(2, "[PISCSI-DEBUGME] Arrived at BootEntry, for some reason.\n"); DEBUGME_SIMPLE(3, "[PISCSI-DEBUGME] Init: Interrupt disable.\n"); DEBUGME_SIMPLE(4, "[PISCSI-DEBUGME] Init: Copy/reloc driver.\n"); DEBUGME_SIMPLE(5, "[PISCSI-DEBUGME] Init: InitResident.\n"); @@ -488,7 +550,10 @@ void piscsi_debugme(uint32_t index) { DEBUGME_SIMPLE(10, "[PISCSI-DEBUGME] Init: AllocMem for resident.\n"); DEBUGME_SIMPLE(11, "[PISCSI-DEBUGME] Init: Checking if resident is loaded.\n"); DEBUGME_SIMPLE(22, "[PISCSI-DEBUGME] Arrived at BootEntry.\n"); - DEBUGME_SIMPLE(30, "[PISCSI-DEBUGME] LoadFileSystems: Opening FileSystem.resource.\n"); + case 30: + DEBUG("[PISCSI-DEBUGME] LoadFileSystems: Opening FileSystem.resource.\n"); + rom_cur_fs = 0; + break; DEBUGME_SIMPLE(33, "[PISCSI-DEBUGME] FileSystem.resource not available, creating.\n"); case 31: DEBUG("[PISCSI-DEBUGME] OpenResource result: %d\n", piscsi_u32[0]); @@ -496,6 +561,12 @@ void piscsi_debugme(uint32_t index) { case 32: DEBUG("AAAAHH!\n"); break; + case 35: + DEBUG("[PISCSI-DEBUGME] stuff output\n"); + break; + case 36: + DEBUG("[PISCSI-DEBUGME] Debug pointers: %.8X %.8X %.8X %.8X\n", piscsi_u32[0], piscsi_u32[1], piscsi_u32[2], piscsi_u32[3]); + break; default: DEBUG("[!!!PISCSI-DEBUGME] No debugme message for index %d!\n", index); break; @@ -508,6 +579,7 @@ void piscsi_debugme(uint32_t index) { void handle_piscsi_write(uint32_t addr, uint32_t val, uint8_t type) { int32_t r; + uint8_t *map; #ifndef PISCSI_DEBUG if (type) {} #endif @@ -519,70 +591,84 @@ void handle_piscsi_write(uint32_t addr, uint32_t val, uint8_t type) { switch (cmd) { case PISCSI_CMD_READ64: case PISCSI_CMD_READ: + case PISCSI_CMD_READBYTES: d = &devs[val]; if (d->fd == -1) { DEBUG("[!!!PISCSI] BUG: Attempted read from unmapped drive %d.\n", val); break; } - if (cmd == PISCSI_CMD_READ) { + if (cmd == PISCSI_CMD_READBYTES) { + DEBUG("[PISCSI-%d] %d byte READBYTES from block %d to address %.8X\n", val, piscsi_u32[1], piscsi_u32[0] / d->block_size, piscsi_u32[2]); + uint32_t src = piscsi_u32[0]; + d->lba = (src / d->block_size); + lseek(d->fd, src, SEEK_SET); + } + else if (cmd == PISCSI_CMD_READ) { DEBUG("[PISCSI-%d] %d byte READ from block %d to address %.8X\n", val, piscsi_u32[1], piscsi_u32[0], piscsi_u32[2]); d->lba = piscsi_u32[0]; - lseek(d->fd, (piscsi_u32[0] * 512), SEEK_SET); + lseek(d->fd, (piscsi_u32[0] * d->block_size), SEEK_SET); } else { uint64_t src = piscsi_u32[3]; src = (src << 32) | piscsi_u32[0]; - DEBUG("[PISCSI-%d] %d byte READ64 from block %lld to address %.8X\n", val, piscsi_u32[1], (src / 512), piscsi_u32[2]); - d->lba = (src / 512); + DEBUG("[PISCSI-%d] %d byte READ64 from block %lld to address %.8X\n", val, piscsi_u32[1], (src / d->block_size), piscsi_u32[2]); + d->lba = (src / d->block_size); lseek64(d->fd, src, SEEK_SET); } - r = get_mapped_item_by_address(cfg, piscsi_u32[2]); - if (r != -1 && cfg->map_type[r] == MAPTYPE_RAM) { + map = get_mapped_data_pointer_by_address(cfg, piscsi_u32[2]); + if (map) { DEBUG_TRIVIAL("[PISCSI-%d] \"DMA\" Read goes to mapped range %d.\n", val, r); - read(d->fd, cfg->map_data[r] + piscsi_u32[2] - cfg->map_offset[r], piscsi_u32[1]); + read(d->fd, map, piscsi_u32[1]); } else { DEBUG_TRIVIAL("[PISCSI-%d] No mapped range found for read.\n", val); uint8_t c = 0; for (uint32_t i = 0; i < piscsi_u32[1]; i++) { read(d->fd, &c, 1); - write8(piscsi_u32[2] + i, (uint32_t)c); + m68k_write_memory_8(piscsi_u32[2] + i, (uint32_t)c); } } break; case PISCSI_CMD_WRITE64: case PISCSI_CMD_WRITE: + case PISCSI_CMD_WRITEBYTES: d = &devs[val]; if (d->fd == -1) { DEBUG ("[PISCSI] BUG: Attempted write to unmapped drive %d.\n", val); break; } - if (cmd == PISCSI_CMD_WRITE) { + if (cmd == PISCSI_CMD_WRITEBYTES) { + DEBUG("[PISCSI-%d] %d byte WRITEBYTES to block %d from address %.8X\n", val, piscsi_u32[1], piscsi_u32[0] / d->block_size, piscsi_u32[2]); + uint32_t src = piscsi_u32[0]; + d->lba = (src / d->block_size); + lseek(d->fd, src, SEEK_SET); + } + else if (cmd == PISCSI_CMD_WRITE) { DEBUG("[PISCSI-%d] %d byte WRITE to block %d from address %.8X\n", val, piscsi_u32[1], piscsi_u32[0], piscsi_u32[2]); d->lba = piscsi_u32[0]; - lseek(d->fd, (piscsi_u32[0] * 512), SEEK_SET); + lseek(d->fd, (piscsi_u32[0] * d->block_size), SEEK_SET); } else { uint64_t src = piscsi_u32[3]; src = (src << 32) | piscsi_u32[0]; - DEBUG("[PISCSI-%d] %d byte WRITE64 to block %lld from address %.8X\n", val, piscsi_u32[1], (src / 512), piscsi_u32[2]); - d->lba = (src / 512); + DEBUG("[PISCSI-%d] %d byte WRITE64 to block %lld from address %.8X\n", val, piscsi_u32[1], (src / d->block_size), piscsi_u32[2]); + d->lba = (src / d->block_size); lseek64(d->fd, src, SEEK_SET); } - r = get_mapped_item_by_address(cfg, piscsi_u32[2]); - if (r != -1) { + map = get_mapped_data_pointer_by_address(cfg, piscsi_u32[2]); + if (map) { DEBUG_TRIVIAL("[PISCSI-%d] \"DMA\" Write comes from mapped range %d.\n", val, r); - write(d->fd, cfg->map_data[r] + piscsi_u32[2] - cfg->map_offset[r], piscsi_u32[1]); + write(d->fd, map, piscsi_u32[1]); } else { DEBUG_TRIVIAL("[PISCSI-%d] No mapped range found for write.\n", val); uint8_t c = 0; for (uint32_t i = 0; i < piscsi_u32[1]; i++) { - c = read8(piscsi_u32[2] + i); + c = m68k_read_memory_8(piscsi_u32[2] + i); write(d->fd, &c, 1); } } @@ -593,24 +679,28 @@ void handle_piscsi_write(uint32_t addr, uint32_t val, uint8_t type) { break; } case PISCSI_CMD_DRVNUM: - //printf("%d ", val); - if (val % 10 != 0) + if (val % 10 != 0) { piscsi_cur_drive = 255; + } else piscsi_cur_drive = val / 10; - if (piscsi_cur_drive > NUM_UNITS) + if (piscsi_cur_drive > NUM_UNITS) { piscsi_cur_drive = 255; - + } if (piscsi_cur_drive != 255) { DEBUG("[PISCSI] (%s) Drive number set to %d (%d)\n", op_type_names[type], piscsi_cur_drive, val); } break; + case PISCSI_CMD_DRVNUMX: + piscsi_cur_drive = val; + DEBUG("[PISCSI] DRVNUMX: %d.\n", val); + break; case PISCSI_CMD_DEBUGME: piscsi_debugme(val); break; - case PISCSI_CMD_DRIVER: { + case PISCSI_CMD_DRIVER: DEBUG("[PISCSI] Driver copy/patch called, destination address %.8X.\n", val); - int r = get_mapped_item_by_address(cfg, val); + r = get_mapped_item_by_address(cfg, val); if (r != -1) { uint32_t addr = val - cfg->map_offset[r]; uint8_t *dst_data = cfg->map_data[r]; @@ -690,7 +780,6 @@ skip_disk:; } break; - } case PISCSI_CMD_NEXTPART: DEBUG("[PISCSI] Switch partition %d -> %d\n", rom_cur_partition, rom_cur_partition + 1); rom_cur_partition++; @@ -704,7 +793,6 @@ skip_disk:; r = get_mapped_item_by_address(cfg, piscsi_u32[2]); if (r != -1) { uint32_t addr = piscsi_u32[2] - cfg->map_offset[r]; - memset(cfg->map_data[r] + addr, 0x00, filesystems[rom_cur_fs].h_info.alloc_size); memcpy(cfg->map_data[r] + addr, filesystems[rom_cur_fs].binary_data, filesystems[rom_cur_fs].h_info.byte_size); filesystems[rom_cur_fs].h_info.base_offset = piscsi_u32[2]; reloc_hunks(filesystems[rom_cur_fs].relocs, cfg->map_data[r] + addr, &filesystems[rom_cur_fs].h_info); @@ -718,28 +806,50 @@ skip_disk:; if (r != -1) { uint32_t addr = val - cfg->map_offset[r]; struct DeviceNode *node = (struct DeviceNode *)(cfg->map_data[r] + addr); -#ifdef PISCSI_DEBUG char *dosID = (char *)&rom_partition_dostype[rom_cur_partition]; -#endif + DEBUG("[PISCSI] Partition DOSType is %c%c%c/%d\n", dosID[0], dosID[1], dosID[2], dosID[3]); for (i = 0; i < piscsi_num_fs; i++) { if (rom_partition_dostype[rom_cur_partition] == filesystems[i].FS_ID) { - node->dn_SegList = htobe32((filesystems[i].handler >> 2)); + node->dn_SegList = htobe32((((filesystems[i].handler) + filesystems[i].h_info.header_size) >> 2)); node->dn_GlobalVec = 0xFFFFFFFF; goto fs_found; } } - DEBUG("[!!!PISCSI] Found no handler for file system!\n"); + node->dn_GlobalVec = 0xFFFFFFFF; + node->dn_SegList = 0; + printf("[!!!PISCSI] Found no handler for file system %c%c%c/%d\n", dosID[0], dosID[1], dosID[2], dosID[3]); fs_found:; DEBUG("[FS-HANDLER] Next: %d Type: %.8X\n", BE(node->dn_Next), BE(node->dn_Type)); DEBUG("[FS-HANDLER] Task: %d Lock: %d\n", BE(node->dn_Task), BE(node->dn_Lock)); DEBUG("[FS-HANDLER] Handler: %d Stacksize: %d\n", BE((uint32_t)node->dn_Handler), BE(node->dn_StackSize)); - DEBUG("[FS-HANDLER] Priority: %d Startup: %d\n", BE((uint32_t)node->dn_Priority), BE(node->dn_Startup)); + DEBUG("[FS-HANDLER] Priority: %d Startup: %d (%.8X)\n", BE((uint32_t)node->dn_Priority), BE(node->dn_Startup), BE(node->dn_Startup)); DEBUG("[FS-HANDLER] SegList: %.8X GlobalVec: %d\n", BE((uint32_t)node->dn_SegList), BE(node->dn_GlobalVec)); DEBUG("[PISCSI] Handler for partition %.8X set to %.8X (%.8X).\n", BE((uint32_t)node->dn_Name), filesystems[i].FS_ID, filesystems[i].handler); } break; } + case PISCSI_CMD_LOADFS: { + DEBUG("[PISCSI] Attempt to load file system for partition %d from disk.\n", rom_cur_partition); + r = get_mapped_item_by_address(cfg, val); + if (r != -1) { + char *dosID = (char *)&rom_partition_dostype[rom_cur_partition]; + filesystems[piscsi_num_fs].binary_data = NULL; + filesystems[piscsi_num_fs].fhb = NULL; + filesystems[piscsi_num_fs].FS_ID = rom_partition_dostype[rom_cur_partition]; + filesystems[piscsi_num_fs].handler = 0; + if (load_fs(&filesystems[piscsi_num_fs], dosID) != -1) { + printf("[FSHD-Late] Loaded file system %c%c%c/%d from fs storage.\n", dosID[0], dosID[1], dosID[2], dosID[3]); + piscsi_u32[3] = piscsi_num_fs; + rom_cur_fs = piscsi_num_fs; + piscsi_num_fs++; + } else { + printf("[FSHD-Late] Failed to load file system %c%c%c/%d from fs storage.\n", dosID[0], dosID[1], dosID[2], dosID[3]); + piscsi_u32[3] = 0xFFFFFFFF; + } + } + break; + } case PISCSI_DBG_VAL1: case PISCSI_DBG_VAL2: case PISCSI_DBG_VAL3: case PISCSI_DBG_VAL4: case PISCSI_DBG_VAL5: case PISCSI_DBG_VAL6: case PISCSI_DBG_VAL7: case PISCSI_DBG_VAL8: { int i = ((addr & 0xFFFF) - PISCSI_DBG_VAL1) / 4; @@ -814,8 +924,8 @@ uint32_t handle_piscsi_read(uint32_t addr, uint8_t type) { return devs[piscsi_cur_drive].s; break; case PISCSI_CMD_BLOCKS: { - uint32_t blox = devs[piscsi_cur_drive].fs / 512; - DEBUG("[PISCSI] %s Read from BLOCKS %d: %d\n", op_type_names[type], piscsi_cur_drive, (uint32_t)(devs[piscsi_cur_drive].fs / 512)); + uint32_t blox = devs[piscsi_cur_drive].fs / devs[piscsi_cur_drive].block_size; + DEBUG("[PISCSI] %s Read from BLOCKS %d: %d\n", op_type_names[type], piscsi_cur_drive, (uint32_t)(devs[piscsi_cur_drive].fs / devs[piscsi_cur_drive].block_size)); DEBUG("fs: %lld (%d)\n", devs[piscsi_cur_drive].fs, blox); return blox; break; @@ -835,6 +945,27 @@ uint32_t handle_piscsi_read(uint32_t addr, uint8_t type) { case PISCSI_CMD_FSSIZE: DEBUG("[PISCSI] Get alloc size of loaded file system: %d\n", filesystems[rom_cur_fs].h_info.alloc_size); return filesystems[rom_cur_fs].h_info.alloc_size; + case PISCSI_CMD_BLOCKSIZE: + DEBUG("[PISCSI] Get block size of drive %d: %d\n", piscsi_cur_drive, devs[piscsi_cur_drive].block_size); + return devs[piscsi_cur_drive].block_size; + case PISCSI_CMD_GET_FS_INFO: { + int i = 0; + uint32_t val = piscsi_u32[1]; + int32_t r = get_mapped_item_by_address(cfg, val); + if (r != -1) { +#ifdef DEBUG_PISCSI + uint32_t addr = val - cfg->map_offset[r]; + char *dosID = (char *)&rom_partition_dostype[rom_cur_partition]; + DEBUG("[PISCSI-GET-FS-INFO] Partition DOSType is %c%c%c/%d\n", dosID[0], dosID[1], dosID[2], dosID[3]); +#endif + for (i = 0; i < piscsi_num_fs; i++) { + if (rom_partition_dostype[rom_cur_partition] == filesystems[i].FS_ID) { + return 0; + } + } + } + return 1; + } default: DEBUG("[!!!PISCSI] WARN: Unhandled %s register read from %.8X\n", op_type_names[type], addr); break; diff --git a/platforms/amiga/piscsi/piscsi.h b/platforms/amiga/piscsi/piscsi.h index c804adb..b938411 100644 --- a/platforms/amiga/piscsi/piscsi.h +++ b/platforms/amiga/piscsi/piscsi.h @@ -72,7 +72,7 @@ struct piscsi_dev { uint32_t lba; uint32_t num_partitions; uint32_t fshd_offs; - // Will parse max eight partitions per disk + uint32_t block_size; struct PartitionBlock *pb[16]; struct RigidDiskBlock *rdb; }; @@ -81,7 +81,7 @@ struct piscsi_fs { struct FileSysHeaderBlock * fhb; uint32_t FS_ID; uint32_t handler; - struct hunk_reloc relocs[512]; + struct hunk_reloc relocs[4096]; struct hunk_info h_info; uint8_t *binary_data; }; @@ -255,10 +255,15 @@ struct FileSysHeaderBlock { }; void piscsi_init(); +void piscsi_shutdown(); void piscsi_map_drive(char *filename, uint8_t index); +void piscsi_unmap_drive(uint8_t index); +struct piscsi_dev *piscsi_get_dev(uint8_t index); void handle_piscsi_write(uint32_t addr, uint32_t val, uint8_t type); uint32_t handle_piscsi_read(uint32_t addr, uint8_t type); void piscsi_find_filesystems(struct piscsi_dev *d); void piscsi_refresh_drives(); + +int load_fs(struct piscsi_fs *fs, char *dosID); diff --git a/platforms/amiga/piscsi/piscsi.rom b/platforms/amiga/piscsi/piscsi.rom index 85add46..59f9d6f 100644 Binary files a/platforms/amiga/piscsi/piscsi.rom and b/platforms/amiga/piscsi/piscsi.rom differ diff --git a/platforms/amiga/piscsi/readme.md b/platforms/amiga/piscsi/readme.md index 70f1b9f..fea0f44 100644 --- a/platforms/amiga/piscsi/readme.md +++ b/platforms/amiga/piscsi/readme.md @@ -8,10 +8,15 @@ While this driver is considered mostly stable, it's still work in progress. Do n * PiSCSI **requires** some Fast RAM to be mapped on your PiStorm to work. * This may change at some point, but for now make sure that you configure at the very least a few megabytes of Fast RAM so that the boot ROM can load and initialize properly. +* ~~PiSCSI **only** supports **512 byte** block size for virtual SCSI devices.~~ + * ~~The block size is hard coded, this will probably be addressed relatively soon (TM). If you need very large partitions, please use PFS3AIO or a similar file system instead of FFS.~~ + * ~~Selecting a different block size may appear to work in some cases, but in reality it does not. Do not change the block size from the default 512 bytes.~~ +* `[WIP]` Theoretically, PiSCSI should now be compatible with any block size up to 64KB, but this is not very thoroughly tested when you are reading this particular piece of text. * Autobooting, Kickstart 2.0 and up * PiSCSI does NOT work with Kickstart 1.3 yet, as it is missing some code needed to properly add boot nodes with old Kickstarts. * Mounting RDSK/RDB disk images, physical devices with a file system the Amiga can use - * PiSCSI does NOT work with UAE single partition disk images prepared and formatted using WinUAE yet. You can check what type the disk image is using a hex editor, if it starts with `RDSK`, it is a full drive RDSK/RDB image, if it starts with `DOS` it is most likely a UAE single partition disk image. + * PiSCSI **does NOT** work with **specifically UAE single partition** disk images prepared and formatted using WinUAE yet. You can check what type the disk image is using a hex editor, if it starts with `RDSK`, it is a full drive RDSK/RDB image, if it starts with `DOS` it is most likely a UAE single partition disk image. + * It **does** however work with any UAE disk image prepared using the **Full drive/RDB mode** selected in the Hardfile settings. Using **Full drive/RDB mode** in for instance WinUAE requires you to click the button labeled with this exact piece of text. * When mounting a physical drive for use with PiSCSI, please read the `A big word of caution` at the bottom of this page * TrackDisk, TrackDisk64 and Direct SCSI @@ -25,7 +30,7 @@ Physical drives can also be mounted using their mount point files on Linux, such You can mount up to 7 disk images using setvar `piscsi0` through `piscsi6`. -For preparing a disk image on your Amiga, you can for instance make a copy of the Workbench tool `HDToolBox`, and then edit the tooltypes (`Icon -> Information` menu) to use `pi-scsi.device` instead of `scsi.device`. +For preparing a disk image **on your Amiga with a PiStorm connected**, you can for instance make a copy of the Workbench tool `HDToolBox`, and then edit the tooltypes (`Icon -> Information` menu) to use `pi-scsi.device` instead of `scsi.device`. **No copying of the pi-scsi.device driver to `DEVS:` or anything like that is necessary, as the driver is loaded from ROM on boot/config time.** @@ -35,7 +40,7 @@ If you want EVEN MORE speed, either adjust the size of your hard drive image so # Making changes to the driver -If you make changes to the driver, you can always test these on the Amiga as a regular file in `DEVS:`, but the Z2 device has to be disabled for this to work properly. Disabling the Z2 device requires you to comment out the line `ac_z2_type[ac_z2_pic_count] = ACTYPE_PISCSI;` in `amiga-platform.c`. +If you make changes to the driver, you can always test these on the Amiga as a regular file in `DEVS:`, but the Z2 device has to be disabled for this to work properly. Disabling the Z2 device requires you to comment out the line `add_z2_pic(ACTYPE_PISCSI, 0);` in `amiga-platform.c`. Steps to create an updated boot ROM, all of these are done in the `device_driver_amiga` directory: @@ -54,7 +59,8 @@ Steps to create an updated boot ROM, all of these are done in the `device_driver Now open a new CLI, and type something like: `giggledisk device=pi-scsi.device unit=0 to=RAM:PI0` -This will create a MountList file called `PI0` on the RAM disk, which contains almost all the information needed to mount the drive and its partitions in Workbench. +This will create a MountList file called `PI0` on the RAM disk, which contains almost all the information needed to mount the drive and its partitions in Workbench. +**Note:** PiSCSI uses the standard nibble ordering for SCSI devices, so the second unit (`piscsi1`) will actually be `unit=10` rather than `unit=1` as you might expect. Similarly, `piscsi2` through `piscsi6` will be `unit=20` through `unit=60`. You'll have to start up your favorite (or least hated) text editor and change the contents of the file a bit. diff --git a/platforms/amiga/pistorm-dev/pistorm-dev-enums.h b/platforms/amiga/pistorm-dev/pistorm-dev-enums.h new file mode 100644 index 0000000..8d85197 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm-dev-enums.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT + +// Currently "2011" / 0x07DB - Defined as "Reserved for Hackers Only" in old Commodore documentation +#define PISTORM_AC_MANUF_ID 0x0, 0x7, 0xD, 0xB + +// [R], [W] and [RW] indicate read, write or both access modes for register +// Any failure or result code from a write command should be put in PI_CMDRESULT +enum pistorm_dev_cmds { + PI_CMD_RESET = 0x00, // [W] Reset the host system. + PI_CMD_SWITCHCONFIG = 0x02, // [W] Switch config file to string at PI_STR1, if it exists. + // This will reset the Amiga if the config loads successfully. + PI_CMD_PISCSI_CTRL = 0x04, // [RW] Write: Control a PiSCSI device. The command written here uses + // values From various data registers around $2000. + // Read: Returns whether PiSCSI is enabled or not. + PI_CMD_RTGSTATUS = 0x06, // [RW] Read: Check RTG status Write: Set RTG status (enabled/disabled) + PI_CMD_NETSTATUS = 0x08, // [RW] Read: Check ETH status Write: Set ETH status (enabled/disabled) + PI_CMD_KICKROM = 0x0A, // [W] Map a different Kickstart ROM to the standard address using + // the string at PI_STR1, if the file exists. Requires some config + // file names to be set in order to find it. + PI_CMD_EXTROM = 0x0E, // [W] Same as above, but the extended ROM. + + PI_CMD_HWREV = 0x10, // [R] Check the PiStorm hardware version/revision + PI_CMD_SWREV = 0x12, // [R] Check the PiStorm software version/revision + + PI_CMD_FILESIZE = 0x0100, // [R] Get the file size for file on the Pi side using the path + // at PI_STR1, if it exists. + PI_CMD_TRANSFERFILE = 0x0104, // [W] Transfer over a file from the Pi to Amiga RAM. + PI_CMD_MEMCPY = 0x0108, // [W] Copy written longword of bytes from one area of memory (PTR1) + // to another (PTR2). + PI_CMD_GET_FB = 0x010C, // [R] Get the current framebuffer address. + PI_CMD_COPYRECT = 0x0110, // [W] Generic memory copyrect with source and destination pitch. + PI_CMD_COPYRECT_EX = 0x0112, // [W] Extended memory copyrect with additional source/destination X/Y coordinates. + PI_CMD_MEMSET = 0x0114, // [W] Accelerated memset functionality to quickly clear a region of memory to a specific value. + PI_CMD_SHOWFPS = 0x0118, // [W] Enable/disable RTG FPS display. + PI_CMD_PALETTEDEBUG = 0x011A, // [W] Enable/disable RTG palette debug. + PI_CMD_MEMCPY_Q = 0x0120, // [W] CopyMemQuick debug thing + + PI_CMD_QBASIC = 0x0FFC, // QBasic + PI_CMD_NIBBLES = 0x0FFE, // Nibbles + + PI_DBG_MSG = 0x1000, // [W] Trigger debug message output to avoid slow serial kprintf. + PI_DBG_VAL1 = 0x1010, // [RW] + PI_DBG_VAL2 = 0x1014, // [RW] + PI_DBG_VAL3 = 0x1018, // [RW] + PI_DBG_VAL4 = 0x101C, // [RW] + PI_DBG_VAL5 = 0x1020, // [RW] + PI_DBG_VAL6 = 0x1024, // [RW] + PI_DBG_VAL7 = 0x1028, // [RW] + PI_DBG_VAL8 = 0x102C, // [RW] + PI_DBG_STR1 = 0x1030, // [W] Pointers to debug strings (typically in Amiga RAM) + PI_DBG_STR2 = 0x1034, // [W] + PI_DBG_STR3 = 0x1038, // [W] + PI_DBG_STR4 = 0x103C, // [W] + + PI_CMD_SHUTDOWN = 0x1FFC, // [W] Initiate requesting the Pi to shut down + PI_CMD_CONFIRMSHUTDOWN = 0x1FFE, // [W] Confirm shutting down the Pi + + PI_BYTE1 = 0x2000, // [RW] // Bytes, words and longwords used as extended arguments. + PI_BYTE2 = 0x2001, // [RW] // for PiStorm interaction device commands. + PI_BYTE3 = 0x2002, // [RW] + PI_BYTE4 = 0x2003, // [RW] + PI_BYTE5 = 0x2004, // [RW] + PI_BYTE6 = 0x2005, // [RW] + PI_BYTE7 = 0x2006, // [RW] + PI_BYTE8 = 0x2007, // [RW] + PI_WORD1 = 0x2008, // [RW] + PI_WORD2 = 0x200A, // [RW] + PI_WORD3 = 0x200C, // [RW] + PI_WORD4 = 0x200E, // [RW] + PI_LONGWORD1 = 0x2010, // [RW] + PI_LONGWORD2 = 0x2014, // [RW] + PI_LONGWORD3 = 0x2018, // [RW] + PI_LONGWORD4 = 0x201C, // [RW] + PI_STR1 = 0x2020, // [W] Pointers to strings (typically in Amiga RAM) + PI_STR2 = 0x2024, // [W] + PI_STR3 = 0x2028, // [W] + PI_STR4 = 0x202C, // [W] + PI_PTR1 = 0x2030, // [W] Pointers to allocated memory in Amiga RAM. + PI_PTR2 = 0x2034, // [W] For instance for loading large files to Amiga RAM or + PI_PTR3 = 0x2038, // [W] transferring over files from the Pi side of things. + PI_PTR4 = 0x203C, // [W] + PI_WORD5 = 0x2040, // [RW] + PI_WORD6 = 0x2042, // [RW] + PI_WORD7 = 0x2044, // [RW] + PI_WORD8 = 0x2046, // [RW] + PI_WORD9 = 0x2048, // [RW] + PI_WORD10 = 0x204A, // [RW] + PI_WORD11 = 0x204C, // [RW] + PI_WORD12 = 0x204E, // [RW] + + PI_CMDRESULT = 0x2100, // [R] Check the result of any command that provides a "return value". +}; + +enum pistorm_piscsi_commands { + PISCSI_CTRL_NONE, + PISCSI_CTRL_MAP, // For hard drives + PISCSI_CTRL_UNMAP, // + PISCSI_CTRL_EJECT, // For optical media, not yet implemented + PISCSI_CTRL_INSERT, // + PISCSI_CTRL_ENABLE, // Enable PiSCSI + PISCSI_CTRL_DISABLE, // Disable PiSCSI + PISCSI_CTRL_NUM, +}; + +enum pistorm_config_commands { + PICFG_LOAD, // Load a config file from string at PI_STR1 + PICFG_RELOAD, // Reload current config file, in case hard drives or ROM has been changed + PICFG_DEFAULT, // Load default.cfg if it exists + PICFG_NUM, +}; + +enum pistorm_command_results { + PI_RES_OK, + PI_RES_FAILED, + PI_RES_NOCHANGE, + PI_RES_FILENOTFOUND, + PI_RES_INVALIDVALUE, + PI_RES_INVALIDCMD, + PI_RES_NUM, +}; diff --git a/platforms/amiga/pistorm-dev/pistorm-dev.c b/platforms/amiga/pistorm-dev/pistorm-dev.c new file mode 100644 index 0000000..84cafa1 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm-dev.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +#include "pistorm-dev.h" +#include "pistorm-dev-enums.h" +#include "platforms/platforms.h" +#include "gpio/ps_protocol.h" +#include "platforms/amiga/rtg/rtg.h" +#include "platforms/amiga/piscsi/piscsi.h" +#include "platforms/amiga/net/pi-net.h" + +#include +#include +#include + +#define DEBUG_PISTORM_DEVICE + +#ifdef DEBUG_PISTORM_DEVICE +#define DEBUG printf + +static const char *op_type_names[4] = { + "BYTE", + "WORD", + "LONGWORD", + "MEM", +}; +#else +#define DEBUG(...) +#endif + +#define PIDEV_SWREV 0x0105 + +extern uint32_t pistorm_dev_base; +extern uint32_t do_reset; + +extern void adjust_ranges_amiga(struct emulator_config *cfg); +extern uint8_t rtg_enabled, rtg_on, pinet_enabled, piscsi_enabled, load_new_config, end_signal; +extern struct emulator_config *cfg; +extern int cpu_emulation_running; + +char cfg_filename[256] = "default.cfg"; +char tmp_string[256]; + +static uint8_t pi_byte[32]; +static uint16_t pi_word[32]; +static uint32_t pi_longword[32]; +static uint32_t pi_string[32]; +static uint32_t pi_ptr[32]; + +static uint32_t pi_dbg_val[32]; +static uint32_t pi_dbg_string[32]; + +static uint32_t pi_cmd_result = 0, shutdown_confirm = 0xFFFFFFFF; + +int32_t grab_amiga_string(uint32_t addr, uint8_t *dest, uint32_t str_max_len) { + int32_t r = get_mapped_item_by_address(cfg, addr); + uint32_t index = 0; + + if (r == -1) { + DEBUG("[GRAB_AMIGA_STRING] No mapped range found for address $%.8X. Grabbing string data over the bus.\n", addr); + do { + dest[index] = (unsigned char)m68k_read_memory_8(addr + index); + index++; + } while (dest[index - 1] != 0x00 && index < str_max_len); + } + else { + uint8_t *src = cfg->map_data[r] + (addr - cfg->map_offset[r]); + do { + dest[index] = src[index]; + index++; + } while (dest[index - 1] != 0x00 && index < str_max_len); + } + if (index == str_max_len) { + memset(dest, 0x00, str_max_len + 1); + return -1; + } + DEBUG("[GRAB_AMIGA_STRING] Grabbed string: %s\n", dest); + return (int32_t)strlen((const char*)dest); +} + +int32_t amiga_transfer_file(uint32_t addr, char *filename) { + FILE *in = fopen(filename, "rb"); + if (in == NULL) { + DEBUG("[AMIGA_TRANSFER_FILE] Failed to open file %s for reading.\n", filename); + return -1; + } + fseek(in, 0, SEEK_END); + + int32_t r = get_mapped_item_by_address(cfg, addr); + uint32_t filesize = ftell(in); + + fseek(in, 0, SEEK_SET); + if (r == -1) { + DEBUG("[GRAB_AMIGA_STRING] No mapped range found for address $%.8X. Transferring file data over the bus.\n", addr); + uint8_t tmp_read = 0; + + for (uint32_t i = 0; i < filesize; i++) { + tmp_read = (uint8_t)fgetc(in); + m68k_write_memory_8(addr + i, tmp_read); + } + } else { + uint8_t *dst = cfg->map_data[r] + (addr - cfg->map_offset[r]); + fread(dst, filesize, 1, in); + } + fclose(in); + DEBUG("[AMIGA_TRANSFER_FILE] Copied %d bytes to address $%.8X.\n", filesize, addr); + + return 0; +} + +char *get_pistorm_devcfg_filename() { + return cfg_filename; +} + +void set_pistorm_devcfg_filename(char *filename) { + strcpy(cfg_filename, filename); +} + +void handle_pistorm_dev_write(uint32_t addr_, uint32_t val, uint8_t type) { + uint32_t addr = (addr_ & 0xFFFF); + + switch((addr)) { + case PI_DBG_MSG: + // Output debug message based on value written and data in val/str registers. + break; + case PI_DBG_VAL1: case PI_DBG_VAL2: case PI_DBG_VAL3: case PI_DBG_VAL4: + case PI_DBG_VAL5: case PI_DBG_VAL6: case PI_DBG_VAL7: case PI_DBG_VAL8: + DEBUG("[PISTORM-DEV] Set DEBUG VALUE %d to %d ($%.8X)\n", (addr - PI_DBG_VAL1) / 4, val, val); + pi_dbg_val[(addr - PI_DBG_VAL1) / 4] = val; + break; + case PI_DBG_STR1: case PI_DBG_STR2: case PI_DBG_STR3: case PI_DBG_STR4: + DEBUG("[PISTORM-DEV] Set DEBUG STRING POINTER %d to $%.8X\n", (addr - PI_DBG_STR1) / 4, val); + pi_dbg_string[(addr - PI_DBG_STR1) / 4] = val; + break; + + case PI_BYTE1: case PI_BYTE2: case PI_BYTE3: case PI_BYTE4: + case PI_BYTE5: case PI_BYTE6: case PI_BYTE7: case PI_BYTE8: + DEBUG("[PISTORM-DEV] Set BYTE %d to %d ($%.2X)\n", addr - PI_BYTE1, (val & 0xFF), (val & 0xFF)); + pi_byte[addr - PI_BYTE1] = (val & 0xFF); + break; + case PI_WORD1: case PI_WORD2: case PI_WORD3: case PI_WORD4: + //DEBUG("[PISTORM-DEV] Set WORD %d to %d ($%.4X)\n", (addr - PI_WORD1) / 2, (val & 0xFFFF), (val & 0xFFFF)); + pi_word[(addr - PI_WORD1) / 2] = (val & 0xFFFF); + break; + case PI_WORD5: case PI_WORD6: case PI_WORD7: case PI_WORD8: + case PI_WORD9: case PI_WORD10: case PI_WORD11: case PI_WORD12: + //DEBUG("[PISTORM-DEV] Set WORD %d to %d ($%.4X)\n", (addr - PI_WORD5) / 2, (val & 0xFFFF), (val & 0xFFFF)); + pi_word[(addr - PI_WORD5) / 2] = (val & 0xFFFF); + break; + case PI_LONGWORD1: case PI_LONGWORD2: case PI_LONGWORD3: case PI_LONGWORD4: + //DEBUG("[PISTORM-DEV] Set LONGWORD %d to %d ($%.8X)\n", (addr - PI_LONGWORD1) / 4, val, val); + pi_longword[(addr - PI_LONGWORD1) / 4] = val; + break; + case PI_STR1: case PI_STR2: case PI_STR3: case PI_STR4: + DEBUG("[PISTORM-DEV] Set STRING POINTER %d to $%.8X\n", (addr - PI_STR1) / 4, val); + pi_string[(addr - PI_STR1) / 4] = val; + break; + case PI_PTR1: case PI_PTR2: case PI_PTR3: case PI_PTR4: + //DEBUG("[PISTORM-DEV] Set DATA POINTER %d to $%.8X\n", (addr - PI_PTR1) / 4, val); + pi_ptr[(addr - PI_PTR1) / 4] = val; + break; + + case PI_CMD_TRANSFERFILE: + DEBUG("[PISTORM-DEV] Write to TRANSFERFILE.\n"); + if (pi_string[0] == 0 || grab_amiga_string(pi_string[0], (uint8_t *)tmp_string, 255) == -1) { + printf("[PISTORM-DEV] No or invalid filename for TRANSFERFILE. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else if (pi_ptr[0] == 0) { + printf("[PISTORM-DEV] Null pointer specified for TRANSFERFILE destination. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else { + if (amiga_transfer_file(pi_ptr[0], tmp_string) == -1) { + pi_cmd_result = PI_RES_FAILED; + } else { + pi_cmd_result = PI_RES_OK; + } + } + pi_string[0] = 0; + pi_ptr[0] = 0; + break; + case PI_CMD_MEMCPY_Q: + DEBUG("CopyMemQuick.\n"); + if ((pi_ptr[0] & 0x03) != 0) { + DEBUG("[!!!PISTORM-DEV] CopyMemQuick src not aligned: %.8X\n", pi_ptr[0]); + } + if (pi_ptr[1] & 0x03) { + DEBUG("[!!!PISTORM-DEV] CopyMemQuick dst not aligned: %.8X\n", pi_ptr[1]); + } + if (val & 0x03) { + DEBUG("[!!!PISTORM-DEV] CopyMemQuick size not aligned: %.8X\n", val); + } + // Fallthrough + case PI_CMD_MEMCPY: + //DEBUG("[PISTORM-DEV} Write to MEMCPY: %d (%.8X)\n", val, val); + if (pi_ptr[0] == 0 || pi_ptr[1] == 0) { + printf("[PISTORM-DEV] MEMCPY from/to null pointer not allowed. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else if (val == 0) { + printf("[PISTORM-DEV] MEMCPY called with size 0. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else { + //DEBUG("[PISTORM-DEV] Copy %d bytes from $%.8X to $%.8X\n", val, pi_ptr[0], pi_ptr[1]); + int32_t src = get_mapped_item_by_address(cfg, pi_ptr[0]); + int32_t dst = get_mapped_item_by_address(cfg, pi_ptr[1]); + if (cfg->map_type[dst] == MAPTYPE_ROM) + break; + if (dst != -1 && src != -1) { + uint8_t *src_ptr = &cfg->map_data[src][(pi_ptr[0] - cfg->map_offset[src])]; + uint8_t *dst_ptr = &cfg->map_data[dst][(pi_ptr[1] - cfg->map_offset[dst])]; + memcpy(dst_ptr, src_ptr, val); + } else { + uint8_t tmp = 0; + uint16_t tmps = 0; + for (uint32_t i = 0; i < val; i++) { + while (i + 2 < val) { + if (src == -1) tmps = (uint16_t)htobe16(m68k_read_memory_16(pi_ptr[0] + i)); + else memcpy(&tmps, &cfg->map_data[src][pi_ptr[0] - cfg->map_offset[src] + i], 2); + + if (dst == -1) m68k_write_memory_16(pi_ptr[1] + i, be16toh(tmps)); + else memcpy(&cfg->map_data[dst][pi_ptr[1] - cfg->map_offset[dst] + i], &tmps, 2); + i += 2; + } + if (src == -1) tmp = (uint8_t)m68k_read_memory_8(pi_ptr[0] + i); + else tmp = cfg->map_data[src][pi_ptr[0] - cfg->map_offset[src] + i]; + + if (dst == -1) m68k_write_memory_8(pi_ptr[1] + i, tmp); + else cfg->map_data[dst][pi_ptr[1] - cfg->map_offset[dst] + i] = tmp; + } + } + //DEBUG("[PISTORM-DEV] Copied %d bytes from $%.8X to $%.8X\n", val, pi_ptr[0], pi_ptr[1]); + } + break; + case PI_CMD_MEMSET: + //DEBUG("[PISTORM-DEV} Write to MEMSET: %d (%.8X)\n", val, val); + if (pi_ptr[0] == 0) { + printf("[PISTORM-DEV] MEMSET with null pointer not allowed. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else if (val == 0) { + printf("[PISTORM-DEV] MEMSET called with size 0. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else { + int32_t dst = get_mapped_item_by_address(cfg, pi_ptr[0]); + if (cfg->map_type[dst] == MAPTYPE_ROM) + break; + if (dst != -1) { + uint8_t *dst_ptr = &cfg->map_data[dst][(pi_ptr[0] - cfg->map_offset[dst])]; + memset(dst_ptr, pi_byte[0], val); + } else { + for (uint32_t i = 0; i < val; i++) { + m68k_write_memory_8(pi_ptr[0] + i, pi_byte[0]); + } + } + } + break; + case PI_CMD_COPYRECT: + case PI_CMD_COPYRECT_EX: + if (pi_ptr[0] == 0 || pi_ptr[1] == 0) { + printf("[PISTORM-DEV] COPYRECT/EX from/to null pointer not allowed. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else if (pi_word[2] == 0 || pi_word[3] == 0) { + printf("[PISTORM-DEV] COPYRECT/EX called with a width/height of 0. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else { + int32_t src = get_mapped_item_by_address(cfg, pi_ptr[0]); + int32_t dst = get_mapped_item_by_address(cfg, pi_ptr[1]); + + if (addr != PI_CMD_COPYRECT_EX) { + // Clear out the src/dst coordinates in case something else set them previously. + pi_word[4] = pi_word[5] = pi_word[6] = pi_word[7] = 0; + } + + if (dst != -1 && src != -1) { + uint8_t *src_ptr = &cfg->map_data[src][(pi_ptr[0] - cfg->map_offset[src])]; + uint8_t *dst_ptr = &cfg->map_data[dst][(pi_ptr[1] - cfg->map_offset[dst])]; + + if (addr == PI_CMD_COPYRECT_EX) { + // Adjust pointers in the case of available src/dst coordinates. + src_ptr += pi_word[4] + (pi_word[5] * pi_word[0]); + dst_ptr += pi_word[6] + (pi_word[7] * pi_word[1]); + } + + for (int i = 0; i < pi_word[3]; i++) { + memcpy(dst_ptr, src_ptr, pi_word[2]); + + src_ptr += pi_word[0]; + dst_ptr += pi_word[1]; + } + } else { + uint32_t src_offset = 0, dst_offset = 0; + uint8_t tmp = 0; + + if (addr == PI_CMD_COPYRECT_EX) { + src_offset += pi_word[4] + (pi_word[5] * pi_word[0]); + dst_offset += pi_word[6] + (pi_word[7] * pi_word[1]); + } + + for (uint32_t y = 0; y < pi_word[3]; y++) { + for (uint32_t x = 0; x < pi_word[2]; x++) { + if (src == -1) tmp = (unsigned char)m68k_read_memory_8(pi_ptr[0] + src_offset + x); + else tmp = cfg->map_data[src][(pi_ptr[0] + src_offset + x) - cfg->map_offset[src]]; + + if (dst == -1) m68k_write_memory_8(pi_ptr[1] + dst_offset + x, tmp); + else cfg->map_data[dst][(pi_ptr[1] + dst_offset + x) - cfg->map_offset[dst]] = tmp; + } + src_offset += pi_word[0]; + dst_offset += pi_word[1]; + } + } + } + break; + + case PI_CMD_SHOWFPS: rtg_show_fps((uint8_t)val); break; + case PI_CMD_PALETTEDEBUG: rtg_palette_debug((uint8_t)val); break; + case PI_CMD_RTGSTATUS: + DEBUG("[PISTORM-DEV] Write to RTGSTATUS: %d\n", val); + if (val == 1 && !rtg_enabled) { + init_rtg_data(cfg); + rtg_enabled = 1; + pi_cmd_result = PI_RES_OK; + } else if (val == 0 && rtg_enabled) { + if (!rtg_on) { + shutdown_rtg(); + rtg_enabled = 0; + pi_cmd_result = PI_RES_OK; + } else { + // Refuse to disable RTG if it's currently in use. + pi_cmd_result = PI_RES_FAILED; + } + } else { + pi_cmd_result = PI_RES_NOCHANGE; + } + adjust_ranges_amiga(cfg); + break; + case PI_CMD_NETSTATUS: + DEBUG("[PISTORM-DEV] Write to NETSTATUS: %d\n", val); + if (val == 1 && !pinet_enabled) { + pinet_init(NULL); + pinet_enabled = 1; + pi_cmd_result = PI_RES_OK; + } else if (val == 0 && pinet_enabled) { + pinet_shutdown(); + pinet_enabled = 0; + pi_cmd_result = PI_RES_OK; + } else { + pi_cmd_result = PI_RES_NOCHANGE; + } + adjust_ranges_amiga(cfg); + break; + case PI_CMD_PISCSI_CTRL: + DEBUG("[PISTORM-DEV] Write to PISCSI_CTRL: "); + switch(val) { + case PISCSI_CTRL_DISABLE: + DEBUG("DISABLE\n"); + if (piscsi_enabled) { + piscsi_shutdown(); + piscsi_enabled = 0; + // Probably not OK... depends on if you booted from floppy, I guess. + pi_cmd_result = PI_RES_OK; + } else { + pi_cmd_result = PI_RES_NOCHANGE; + } + break; + case PISCSI_CTRL_ENABLE: + DEBUG("ENABLE\n"); + if (!piscsi_enabled) { + piscsi_init(); + piscsi_enabled = 1; + piscsi_refresh_drives(); + pi_cmd_result = PI_RES_OK; + } else { + pi_cmd_result = PI_RES_NOCHANGE; + } + break; + case PISCSI_CTRL_MAP: + DEBUG("MAP\n"); + if (pi_string[0] == 0 || grab_amiga_string(pi_string[0], (uint8_t *)tmp_string, 255) == -1) { + printf("[PISTORM-DEV] Failed to grab string for PISCSI drive filename. Aborting.\n"); + pi_cmd_result = PI_RES_FAILED; + } else { + FILE *tmp = fopen(tmp_string, "rb"); + if (tmp == NULL) { + printf("[PISTORM-DEV] Failed to open file %s for PISCSI drive mapping. Aborting.\n", tmp_string); + pi_cmd_result = PI_RES_FILENOTFOUND; + } else { + fclose(tmp); + printf("[PISTORM-DEV] Attempting to map file %s as PISCSI drive %d...\n", tmp_string, pi_word[0]); + piscsi_unmap_drive(pi_word[0]); + piscsi_map_drive(tmp_string, pi_word[0]); + pi_cmd_result = PI_RES_OK; + } + } + pi_string[0] = 0; + break; + case PISCSI_CTRL_UNMAP: + DEBUG("UNMAP\n"); + if (pi_word[0] > 7) { + printf("[PISTORM-DEV] Invalid drive ID %d for PISCSI unmap command.", pi_word[0]); + pi_cmd_result = PI_RES_INVALIDVALUE; + } else { + if (piscsi_get_dev(pi_word[0])->fd != -1) { + piscsi_unmap_drive(pi_word[0]); + pi_cmd_result = PI_RES_OK; + } else { + pi_cmd_result = PI_RES_NOCHANGE; + } + } + break; + case PISCSI_CTRL_EJECT: + DEBUG("EJECT (NYI)\n"); + pi_cmd_result = PI_RES_NOCHANGE; + break; + case PISCSI_CTRL_INSERT: + DEBUG("INSERT (NYI)\n"); + pi_cmd_result = PI_RES_NOCHANGE; + break; + default: + DEBUG("UNKNOWN/UNHANDLED. Aborting.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + break; + } + adjust_ranges_amiga(cfg); + break; + + case PI_CMD_KICKROM: + DEBUG("[PISTORM-DEV] Write to KICKROM.\n"); + if (pi_string[0] == 0 || grab_amiga_string(pi_string[0], (uint8_t *)tmp_string, 255) == -1) { + printf("[PISTORM-DEV] Failed to grab string KICKROM filename. Aborting.\n"); + pi_cmd_result = PI_RES_FAILED; + } else { + FILE *tmp = fopen(tmp_string, "rb"); + if (tmp == NULL) { + printf("[PISTORM-DEV] Failed to open file %s for KICKROM mapping. Aborting.\n", tmp_string); + pi_cmd_result = PI_RES_FILENOTFOUND; + } else { + fclose(tmp); + if (get_named_mapped_item(cfg, "kickstart") != -1) { + uint32_t index = get_named_mapped_item(cfg, "kickstart"); + free(cfg->map_data[index]); + free(cfg->map_id[index]); + cfg->map_type[index] = MAPTYPE_NONE; + // Dirty hack, I am sleepy and lazy. + add_mapping(cfg, MAPTYPE_ROM, cfg->map_offset[index], cfg->map_size[index], 0, tmp_string, "kickstart", 0); + pi_cmd_result = PI_RES_OK; + do_reset = 1; + } else { + printf ("[PISTORM-DEV] Could not find mapped range 'kickstart', cannot remap KICKROM.\n"); + pi_cmd_result = PI_RES_FAILED; + } + } + } + adjust_ranges_amiga(cfg); + pi_string[0] = 0; + break; + case PI_CMD_EXTROM: + DEBUG("[PISTORM-DEV] Write to EXTROM.\n"); + if (pi_string[0] == 0 || grab_amiga_string(pi_string[0], (uint8_t *)tmp_string, 255) == -1) { + printf("[PISTORM-DEV] Failed to grab string EXTROM filename. Aborting.\n"); + pi_cmd_result = PI_RES_FAILED; + } else { + FILE *tmp = fopen(tmp_string, "rb"); + if (tmp == NULL) { + printf("[PISTORM-DEV] Failed to open file %s for EXTROM mapping. Aborting.\n", tmp_string); + pi_cmd_result = PI_RES_FILENOTFOUND; + } else { + fclose(tmp); + if (get_named_mapped_item(cfg, "extended") != -1) { + uint32_t index = get_named_mapped_item(cfg, "extended"); + free(cfg->map_data[index]); + free(cfg->map_id[index]); + cfg->map_type[index] = MAPTYPE_NONE; + // Dirty hack, I am tired and lazy. + add_mapping(cfg, MAPTYPE_ROM, cfg->map_offset[index], cfg->map_size[index], 0, tmp_string, "extended", 0); + pi_cmd_result = PI_RES_OK; + do_reset = 1; + } else { + printf ("[PISTORM-DEV] Could not find mapped range 'extrom', cannot remap EXTROM.\n"); + pi_cmd_result = PI_RES_FAILED; + } + } + } + adjust_ranges_amiga(cfg); + pi_string[0] = 0; + break; + + case PI_CMD_RESET: + DEBUG("[PISTORM-DEV] System reset called, code %d\n", (val & 0xFFFF)); + do_reset = 1; + break; + case PI_CMD_SHUTDOWN: + DEBUG("[PISTORM-DEV] Shutdown requested. Confirm by replying with return value to CONFIRMSHUTDOWN.\n"); + shutdown_confirm = rand() % 0xFFFF; + pi_cmd_result = shutdown_confirm; + break; + case PI_CMD_CONFIRMSHUTDOWN: + if (val != shutdown_confirm) { + DEBUG("[PISTORM-DEV] Attempted shutdown with wrong shutdown confirm value. Not shutting down.\n"); + shutdown_confirm = 0xFFFFFFFF; + pi_cmd_result = PI_RES_FAILED; + } else { + printf("[PISTORM-DEV] Shutting down the PiStorm. Good night, fight well, until we meet again.\n"); + reboot(LINUX_REBOOT_CMD_POWER_OFF); + pi_cmd_result = PI_RES_OK; + end_signal = 1; + } + break; + case PI_CMD_SWITCHCONFIG: + DEBUG("[PISTORM-DEV] Config switch called, command: "); + switch (val) { + case PICFG_LOAD: + DEBUG("LOAD\n"); + if (pi_string[0] == 0 || grab_amiga_string(pi_string[0], (uint8_t *)cfg_filename, 255) == -1) { + printf("[PISTORM-DEV] Failed to grab string for CONFIG filename. Aborting.\n"); + pi_cmd_result = PI_RES_FAILED; + } else { + FILE *tmp = fopen(cfg_filename, "rb"); + if (tmp == NULL) { + printf("[PISTORM-DEV] Failed to open CONFIG file %s for reading. Aborting.\n", cfg_filename); + pi_cmd_result = PI_RES_FILENOTFOUND; + } else { + fclose(tmp); + printf("[PISTORM-DEV] Attempting to load config file %s...\n", cfg_filename); + load_new_config = val + 1; + pi_cmd_result = PI_RES_OK; + } + } + pi_string[0] = 0; + break; + case PICFG_RELOAD: + DEBUG("RELOAD\n"); + printf("[PISTORM-DEV] Reloading current config file (%s)...\n", cfg_filename); + load_new_config = val + 1; + break; + case PICFG_DEFAULT: + DEBUG("DEFAULT\n"); + printf("[PISTORM-DEV] Loading default.cfg...\n"); + load_new_config = val + 1; + break; + default: + DEBUG("UNKNOWN/UNHANDLED. Command ignored.\n"); + pi_cmd_result = PI_RES_INVALIDVALUE; + break; + } + break; + default: + DEBUG("[PISTORM-DEV] WARN: Unhandled %s register write to %.4X: %d\n", op_type_names[type], addr - pistorm_dev_base, val); + pi_cmd_result = PI_RES_INVALIDCMD; + break; + } +} + +uint32_t handle_pistorm_dev_read(uint32_t addr_, uint8_t type) { + uint32_t addr = (addr_ & 0xFFFF); + + switch((addr)) { + case PI_CMD_FILESIZE: + DEBUG("[PISTORM-DEV] %s read from FILESIZE.\n", op_type_names[type]); + if (pi_string[0] == 0 || grab_amiga_string(pi_string[0], (uint8_t *)tmp_string, 255) == -1) { + DEBUG("[PISTORM-DEV] Failed to grab string for FILESIZE command. Aborting.\n"); + pi_cmd_result = PI_RES_FAILED; + pi_longword[0] = 0; + return 0; + } else { + FILE *tmp = fopen(tmp_string, "rb"); + if (tmp == NULL) { + DEBUG("[PISTORM-DEV] Failed to open file %s for FILESIZE command. Aborting.\n", tmp_string); + pi_longword[0] = 0; + pi_cmd_result = PI_RES_FILENOTFOUND; + } else { + fseek(tmp, 0, SEEK_END); + pi_longword[0] = ftell(tmp); + DEBUG("[PISTORM-DEV] Returning file size for file %s: %d bytes.\n", tmp_string, pi_longword[0]); + fclose(tmp); + pi_cmd_result = PI_RES_OK; + } + } + pi_string[0] = 0; + return pi_longword[0]; + break; + + case PI_CMD_HWREV: + // Probably replace this with some read from the CPLD to get a simple hardware revision. + DEBUG("[PISTORM-DEV] %s Read from HWREV\n", op_type_names[type]); + return 0x0101; // 1.1 + break; + case PI_CMD_SWREV: + DEBUG("[PISTORM-DEV] %s Read from SWREV\n", op_type_names[type]); + return PIDEV_SWREV; + break; + case PI_CMD_RTGSTATUS: + DEBUG("[PISTORM-DEV] %s Read from RTGSTATUS\n", op_type_names[type]); + return (rtg_on << 1) | rtg_enabled; + break; + case PI_CMD_NETSTATUS: + DEBUG("[PISTORM-DEV] %s Read from NETSTATUS\n", op_type_names[type]); + return pinet_enabled; + break; + case PI_CMD_PISCSI_CTRL: + DEBUG("[PISTORM-DEV] %s Read from PISCSI_CTRL\n", op_type_names[type]); + return piscsi_enabled; + break; + case PI_CMD_GET_FB: + //DEBUG("[PISTORM-DEV] %s read from GET_FB: %.8X\n", op_type_names[type], rtg_get_fb()); + return rtg_get_fb(); + + case PI_DBG_VAL1: case PI_DBG_VAL2: case PI_DBG_VAL3: case PI_DBG_VAL4: + case PI_DBG_VAL5: case PI_DBG_VAL6: case PI_DBG_VAL7: case PI_DBG_VAL8: + DEBUG("[PISTORM-DEV] Read DEBUG VALUE %d (%d / $%.8X)\n", (addr - PI_DBG_VAL1) / 4, pi_dbg_val[(addr - PI_DBG_VAL1) / 4], pi_dbg_val[(addr - PI_DBG_VAL1) / 4]); + return pi_dbg_val[(addr - PI_DBG_VAL1) / 4]; + break; + + case PI_BYTE1: case PI_BYTE2: case PI_BYTE3: case PI_BYTE4: + case PI_BYTE5: case PI_BYTE6: case PI_BYTE7: case PI_BYTE8: + DEBUG("[PISTORM-DEV] Read BYTE %d (%d / $%.2X)\n", addr - PI_BYTE1, pi_byte[addr - PI_BYTE1], pi_byte[addr - PI_BYTE1]); + return pi_byte[addr - PI_BYTE1]; + break; + case PI_WORD1: case PI_WORD2: case PI_WORD3: case PI_WORD4: + DEBUG("[PISTORM-DEV] Read WORD %d (%d / $%.4X)\n", (addr - PI_WORD1) / 2, pi_word[(addr - PI_WORD1) / 2], pi_word[(addr - PI_WORD1) / 2]); + return pi_word[(addr - PI_WORD1) / 2]; + break; + case PI_WORD5: case PI_WORD6: case PI_WORD7: case PI_WORD8: + case PI_WORD9: case PI_WORD10: case PI_WORD11: case PI_WORD12: + DEBUG("[PISTORM-DEV] Read WORD %d (%d / $%.4X)\n", (addr - PI_WORD5) / 2, pi_word[(addr - PI_WORD5) / 2], pi_word[(addr - PI_WORD5) / 2]); + return pi_word[(addr - PI_WORD5) / 2]; + break; + case PI_LONGWORD1: case PI_LONGWORD2: case PI_LONGWORD3: case PI_LONGWORD4: + DEBUG("[PISTORM-DEV] Read LONGWORD %d (%d / $%.8X)\n", (addr - PI_LONGWORD1) / 4, pi_longword[(addr - PI_LONGWORD1) / 4], pi_longword[(addr - PI_LONGWORD1) / 4]); + return pi_longword[(addr - PI_LONGWORD1) / 4]; + break; + + case PI_CMDRESULT: + //DEBUG("[PISTORM-DEV] %s Read from CMDRESULT: %d\n", op_type_names[type], pi_cmd_result); + return pi_cmd_result; + break; + + default: + DEBUG("[PISTORM-DEV] WARN: Unhandled %s register read from %.4X\n", op_type_names[type], addr - pistorm_dev_base); + break; + } + return 0; +} diff --git a/platforms/amiga/pistorm-dev/pistorm-dev.h b/platforms/amiga/pistorm-dev/pistorm-dev.h new file mode 100644 index 0000000..5520521 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm-dev.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +#include + +void handle_pistorm_dev_write(uint32_t addr, uint32_t val, uint8_t type); +uint32_t handle_pistorm_dev_read(uint32_t addr, uint8_t type); +char *get_pistorm_devcfg_filename(); +void set_pistorm_devcfg_filename(char *filename); diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/CopyMems b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/CopyMems new file mode 100755 index 0000000..70c845e Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/CopyMems differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/Makefile b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/Makefile new file mode 100644 index 0000000..af70e25 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/Makefile @@ -0,0 +1,12 @@ +CC=vc +kick13 +CFLAGS=-c99 -DNDEBUG -lamiga -lauto + +all: PiSimple PiStorm + +PiSimple: simple_interact.c pistorm_dev.c + $(CC) $(CFLAGS) $^ -o PiSimple + +PiStorm: gui_interact.c pistorm_dev.c reqtoolsnb.lib + $(CC) $(CFLAGS) -Ireqtools $^ -o PiStorm + + diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiSimple b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiSimple new file mode 100755 index 0000000..247706a Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiSimple differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiStorm b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiStorm new file mode 100755 index 0000000..7f1401c Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiStorm differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiStorm.info b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiStorm.info new file mode 100644 index 0000000..9c216f2 Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/PiStorm.info differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/README.md b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/README.md new file mode 100644 index 0000000..de07a50 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/README.md @@ -0,0 +1,47 @@ +# PiStorm Interaction Tools + +## Cross-Compiling + +Compiling the tool requires VBCC setup with the kickstart 1.3 libraries. For information on how to set this up in Linux please see https://linuxjedi.co.uk/2021/02/27/using-vbcc-as-an-amiga-cross-compiler-in-linux/ + +Once you have the tooling setup, just run `make`. + +## Installation + +You will need `reqtools.library` in the `libs:` drive. Some Workbench builds come with this. If you don't have it use the version in libs13 for Workbench 1.3 and libs20 for Workbench 2.0 onwards. + +## Tools + +### PiSimple + +PiSimple is a command line tool to interact with PiStorm. Running it without any parameters will give you detailed usage information for the tool. + +### PiStorm + +The PiStorm tool is a GUI tool that implements the same interaction API as the command line tool. It is compatible with Workbench 1.3 onwards. + +#### RTG + +You can enable / disable RTG on-the-fly with the "Enable/Disable RTG" button. This will have a status next to it which indicates the current state of the RTG. + +### Config file + +It is possible to switch the configuration file PiStorm is using. You can either type a name for the config file relative to the PiStorm's execution directory and hit "Commit" or hit "Load Default". If the config file is valid the PiStorm will load it in and the Amiga will immediately reboot. + +### Get file + +You can copy a file from the PiStorm to the Amiga. First of all, type in the filename and path relative to the pistorm directory. Then optionally set the destination directory (can be left blank for the same directory PiStorm was executed in) and hit "Retrieve". + +### Reboot + +Reboots the Amiga (not PiStorm). + +### Kickstart file + +This will let you choose a Kickstart ROM file on the Pi to boot from. As with Config gile, you can type the name of the Kickstart ROM file relative to the PiStorm's execution directory and hit "Commit". If the Kickstart ROM file is valie the PiStorm will load it in and the Amiga will immediately reboot. + +**NOTE:** Instead of rebooting the Amiga may crash here. This would be due to an interrupt trying to access an old ROM position in the new ROM file before the reboot could execute. You can resolve this by manually doing a Ctrl-A-A reboot. + +### Shutdown Pi + +When pressing this button and confirming, the Pi will safely shutdown the underlying Linux OS. When this happens the Amiga will essentially hang as it will be as if the CPU has been removed. There will be no indication that the Pi shutdown has completed so it is probably wise to wait 10-15 seconds before removing power if this option is used. diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/build.sh b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/build.sh new file mode 100755 index 0000000..be0c259 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/build.sh @@ -0,0 +1,2 @@ +m68k-amigaos-gcc pistorm_dev.c simple_interact.c -mregparm -m68020 -O2 -o PiSimple -Wno-unused-parameter -noixemul -DHAS_STDLIB -Wall +m68k-amigaos-gcc pistorm_dev.c copymems.c -m68020 -O2 -o CopyMems -Wno-unused-parameter -noixemul -DHAS_STDLIB -Wall -Wall -Wpedantic diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/copymems.c b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/copymems.c new file mode 100644 index 0000000..e1919b1 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/copymems.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "pistorm_dev.h" +#include "../pistorm-dev-enums.h" + +#include +#include + +struct ExecBase *SysBase; + +char *oldtaskname; +struct Task *task; + +void (*oldCopyMem)(unsigned char *asm("a0"), unsigned char *asm("a1"), unsigned int asm("d0")); +void (*oldCopyMemQuick)(unsigned char *asm("a0"), unsigned char *asm("a1"), unsigned int asm("d0")); +APTR oldCopyMemPtr; +APTR oldCopyMemQuickPtr; + +struct Screen *(*oldOpenScreenTagList)(struct NewScreen *n asm("a0"), struct TagItem *asm("a1")); +struct Screen *(*oldOpenScreen)(struct NewScreen *asm("a0")); + +#define NORM_MONITOR_ID ((options.bits.norm_mon==1)?(PAL_MONITOR_ID):((options.bits.norm_mon==2)?(NTSC_MONITOR_ID):(DEFAULT_MONITOR_ID))) +#define AUTO_MONITOR_ID ((options.bits.auto_mon==0)?(PAL_MONITOR_ID):((options.bits.auto_mon==1)?(NTSC_MONITOR_ID):(DEFAULT_MONITOR_ID))) + +extern unsigned int pistorm_base_addr; +#define WRITELONG(cmd, val) *(volatile unsigned int *)((unsigned int)(pistorm_base_addr+cmd)) = val; + +void pi_CopyMem(unsigned char *src asm("a0"), unsigned char *dst asm("a1"), unsigned int size asm("d0")) { + WRITELONG(PI_PTR1, (unsigned int)src); + WRITELONG(PI_PTR2, (unsigned int)dst); + WRITELONG(PI_CMD_MEMCPY, size); +} + +void pi_CopyMemQuick(unsigned char *src asm("a0"), unsigned char *dst asm("a1"), unsigned int size asm("d0")) { + WRITELONG(PI_PTR1, (unsigned int)src); + WRITELONG(PI_PTR2, (unsigned int)dst); + WRITELONG(PI_CMD_MEMCPY_Q, size); +} + +int leave(int x) +{ + Forbid(); + task->tc_Node.ln_Name = oldtaskname; + Permit(); + return(x); +} + +ULONG pistorm_addr = 0xFFFFFFFF; + +int main(int argc,char *argv[]) +{ + char *task_name = "PiMems "; + SysBase = *(struct ExecBase **)4L; + + unsigned char no_quit = 1; + + task = (struct Task *)FindTask((STRPTR)&task_name); + + if(task) + return(0); + + task = (struct Task *)FindTask(NULL); + oldtaskname = task->tc_Node.ln_Name; + + Forbid(); + task->tc_Node.ln_Name = (char *)&task_name; + Permit(); + + pistorm_addr = pi_find_pistorm(); + if (pistorm_addr == 0xFFFFFFFF) { + printf("We dead!\n"); + return(leave(50)); + } else { + pistorm_base_addr = pistorm_addr; + } + + oldCopyMemPtr = (APTR)SetFunction((struct Library *)SysBase, -0x270, pi_CopyMem); + + oldCopyMemQuickPtr = (APTR)SetFunction((struct Library *)SysBase, -0x276, pi_CopyMemQuick); + //oldCopyMemQuickPtr = (APTR)SetFunction((struct Library *)SysBase, -0x276, pi_CopyMem); + + do + { + Wait(SIGBREAKF_CTRL_C); + } + while(no_quit); + + return(leave(0)); +} diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/gui_interact.c b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/gui_interact.c new file mode 100644 index 0000000..b18a556 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/gui_interact.c @@ -0,0 +1,765 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pistorm_dev.h" +#include "../pistorm-dev-enums.h" + +#include +#include +#include +extern unsigned int pistorm_base_addr; +struct ReqToolsBase *ReqToolsBase; + +#define VERSION "v0.3.4" + +#define button1w 54 +#define button1h 11 + +#define button2w 87 +#define button2h 11 + +#define button3w 100 +#define button3h 11 + +#define tbox1w 130 +#define tbox1h 10 + +#define tbox2w 132 +#define tbox2h 12 + +#define statusbarw 507 +#define statusbarh 10 + +struct TextAttr font = +{ + "topaz.font", + 8, + FS_NORMAL, + 0 +}; + +SHORT SharedBordersPairs0[] = +{ + 0, 0, 0, button1h - 1, 1, button1h - 2, 1, 0, button1w - 2, 0 +}; +SHORT SharedBordersPairs1[] = +{ + 1, button1h - 1, button1w - 2, button1h - 1, button1w - 2, 1, button1w - 1, 0, button1w - 1, button1h - 1 +}; + +SHORT SharedBordersPairs2[] = +{ + 0, 0, 0, button3h - 1, 1, button3h - 2, 1, 0, button3w - 2, 0 +}; +SHORT SharedBordersPairs3[] = +{ + 1, button3h - 1, button3w - 2, button3h - 1, button3w - 2, 1, button3w - 1, 0, button3w - 1, button3h - 1 +}; + +SHORT SharedBordersPairs4[] = +{ + 0, 0, 0, button2h - 1, 1, button2h - 2, 1, 0, button2w - 2, 0 +}; +SHORT SharedBordersPairs5[] = +{ + 1, button2h - 1, button2w - 2, button2h - 1, button2w - 2, 1, button2w - 1, 0, button2w - 1, button2h - 1 +}; + +SHORT SharedBordersPairs6[] = +{ + -2, -1, -2, tbox1h - 1, -1, tbox1h - 2, -1, -1, tbox1w - 2, -1 +}; + +SHORT SharedBordersPairs7[] = +{ + -1, tbox1h - 1, tbox1w - 2, tbox1h - 1, tbox1w - 2, 0, tbox1w - 1, -1, tbox1w - 1, tbox1h - 1 +}; + +SHORT SharedBordersPairs8[] = +{ + 0, 0, statusbarw - 2, 0, statusbarw - 2, 0, 0, 0, 0, 0 +}; + +SHORT SharedBordersPairs9[] = +{ + 0, 0, 0, tbox2h - 1, 1, tbox2h - 2, 1, 0, tbox2w - 2, 0 +}; +SHORT SharedBordersPairs10[] = +{ + 1, tbox2h - 1, tbox2w - 2, tbox2h - 1, tbox2w - 2, 1, tbox2w - 1, 0, tbox2w - 1, tbox2h - 1 +}; + + +struct Border SharedBorders[] = +{ + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs0[0], &SharedBorders[1], // Button 1 + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs1[0], NULL, + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs2[0], &SharedBorders[3], // Button 3 + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs3[0], NULL, + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs4[0], &SharedBorders[5], // Button 2 + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs5[0], NULL, + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs6[0], &SharedBorders[7], // TBox + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs7[0], NULL, + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs8[0], NULL, // Statusbar + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs9[0], &SharedBorders[10], // TBox inverted + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs10[0], NULL, +}; + +struct Border SharedBordersInvert[] = +{ + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs0[0], &SharedBordersInvert[1], // Button 1 + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs1[0], NULL, + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs2[0], &SharedBordersInvert[3], // Button 3 + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs3[0], NULL, + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs4[0], &SharedBordersInvert[5], // Button 2 + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs5[0], NULL, + 0, 0, 1, 0, JAM2, 5, (SHORT *) &SharedBordersPairs9[0], &SharedBordersInvert[7], // TBox inverted + 0, 0, 2, 0, JAM2, 5, (SHORT *) &SharedBordersPairs10[0], NULL, +}; + +struct IntuiText KickstartCommit_text = +{ + 1, 0, JAM2, 2, 2, &font, (UBYTE *)"Commit", NULL +}; + +#define GADKICKSTARTCOMMIT 14 + +struct Gadget KickstartCommit = +{ + NULL, 401, 49, button1w, button1h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[0], (APTR) &SharedBordersInvert[0], + &KickstartCommit_text, 0, NULL, GADKICKSTARTCOMMIT, NULL +}; + + +UBYTE KickstartFileValue_buf[255]; + +struct StringInfo KickstartFileValue = +{ + KickstartFileValue_buf, NULL, 0, 255, 0, 0, 0, 0, 4, 4, NULL, 0, NULL +}; + +struct IntuiText KickstartFile_text = +{ + 1, 0, JAM2, 0, -10, &font, "Kickstart file:", NULL +}; + +#define GADKICKSTARTFILE 13 + +struct Gadget KickstartFile = +{ + &KickstartCommit, 266, 50, tbox1w, tbox1h, + GADGHIMAGE, + 0, + STRGADGET, + (APTR) &SharedBorders[6], NULL, + &KickstartFile_text, 0, (APTR)&KickstartFileValue, GADKICKSTARTFILE, NULL +}; + +struct IntuiText ShutdownButton_text = +{ + 1, 0, JAM2, 2, 2, &font, (UBYTE *)"Shutdown Pi", NULL +}; + +#define GADSHUTDOWN 12 + +struct Gadget ShutdownButton = +{ + &KickstartFile, 60, 166, button3w, button3h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[2], (APTR) &SharedBordersInvert[2], + &ShutdownButton_text, 0, NULL, GADSHUTDOWN, NULL +}; + + +UBYTE DestinationValue_buf[255]; + +struct IntuiText Destination_text[] = +{ + 1, 0, JAM2, -98, 1, &font, "Destination:", &Destination_text[1], + 1, 0, JAM2, 1, 1, &font, DestinationValue_buf, NULL, +}; + +#define GADGETDESTINATION 11 + +struct Gadget GetDestination = +{ + &ShutdownButton, 107, 105, tbox2w, tbox2h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[9], (APTR) &SharedBordersInvert[6], + Destination_text, 0, NULL, GADGETDESTINATION, NULL +}; + +struct IntuiText RebootButton_text = +{ + 1, 0, JAM2, 2, 2, &font, (UBYTE *)"Reboot", NULL +}; + +#define GADREBOOT 10 + +struct Gadget RebootButton = +{ + &GetDestination, 4, 166, button1w, button1h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[0], (APTR) &SharedBordersInvert[0], + &RebootButton_text, 0, NULL, GADREBOOT, NULL +}; + +UBYTE StatusBar_buf[128] = "Reticulating splines..."; + +struct IntuiText StatusBar_text = +{ + 1, 0, JAM2, 4, 2, &font, (UBYTE *)StatusBar_buf, NULL +}; + +#define GADSTATUSBAR 9 + +struct Gadget StatusBar = +{ + &RebootButton, 3, 188, 508, 10, + GADGHIMAGE, + 0, + BOOLGADGET, + (APTR) &SharedBorders[8], NULL, + &StatusBar_text, 0, NULL, GADSTATUSBAR, NULL +}; + + +struct IntuiText RetrieveButton_text = +{ + 1, 0, JAM2, 10, 2, &font, (UBYTE *)"Retrieve", NULL +}; + +#define GADRETRIEVEBUTTON 8 + +struct Gadget RetrieveButton = +{ + &StatusBar, 244, 99, button2w, button2h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[4], (APTR) &SharedBordersInvert[4], + &RetrieveButton_text, 0, NULL, GADRETRIEVEBUTTON, NULL +}; + +UBYTE GetFileValue_buf[255]; + +struct StringInfo GetFileValue = +{ + GetFileValue_buf, NULL, 0, 255, 0, 0, 0, 0, 4, 4, NULL, 0, NULL +}; + +struct IntuiText GetFile_text[] = +{ + 1, 0, JAM2, -98, -10, &font, "Get file from PiStorm:", &GetFile_text[1], + 1, 0, JAM2, -59, 1, &font, "Source:", NULL, +}; + +#define GADGETFILE 7 + +struct Gadget GetFile = +{ + &RetrieveButton, 108, 93, tbox1w, tbox1h, + GADGHIMAGE, + 0, + STRGADGET, + (APTR) &SharedBorders[6], NULL, + GetFile_text, 0, (APTR)&GetFileValue, GADGETFILE, NULL +}; + +struct IntuiText ConfigDefault_text = +{ + 1, 0, JAM2, 2, 2, &font, (UBYTE *)"Load Default", NULL +}; + +#define GADCONFIGDEFAULT 6 + +struct Gadget ConfigDefault = +{ + &GetFile, 9, 62, button3w, button3h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[2], (APTR) &SharedBordersInvert[2], + &ConfigDefault_text, 0, NULL, GADCONFIGDEFAULT, NULL +}; + +struct IntuiText ConfigCommit_text = +{ + 1, 0, JAM2, 2, 2, &font, (UBYTE *)"Commit", NULL +}; + +#define GADCONFIGCOMMIT 5 + +struct Gadget ConfigCommit = +{ + &ConfigDefault, 144, 49, button1w, button1h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[0], (APTR) &SharedBordersInvert[0], + &ConfigCommit_text, 0, NULL, GADCONFIGCOMMIT, NULL +}; + + +UBYTE ConfigFileValue_buf[255]; + +struct StringInfo ConfigFileValue = +{ + ConfigFileValue_buf, NULL, 0, 255, 0, 0, 0, 0, 4, 4, NULL, 0, NULL +}; + +struct IntuiText ConfigFile_text = +{ + 1, 0, JAM2, 0, -10, &font, "Config file:", NULL +}; + +#define GADCONFIGFILE 4 + +struct Gadget ConfigFile = +{ + &ConfigCommit, 10, 50, tbox1w, tbox1h, + GADGHIMAGE, + 0, + STRGADGET, + (APTR) &SharedBorders[6], NULL, + &ConfigFile_text, 0, (APTR)&ConfigFileValue, GADCONFIGFILE, NULL +}; + +UBYTE RTGStatus_buf[64] = "RTG status"; + +struct IntuiText RTGStatus_text = +{ + 1, 0, JAM2, 1, 1, &font, (UBYTE *)RTGStatus_buf, NULL +}; + +#define GADRTGSTATUS 3 + +struct Gadget RTGStatus = +{ + &ConfigFile, 10, 20, tbox1w, tbox1h, + GADGHIMAGE, + 0, + BOOLGADGET, + (APTR) &SharedBorders[6], NULL, + &RTGStatus_text, 0, NULL, GADRTGSTATUS, NULL +}; + +UBYTE RTG_buf[64] = "RTG Enable"; + +struct IntuiText RTG_text = +{ + 1, 0, JAM2, 8, 2, &font, (UBYTE *)RTG_buf, NULL +}; + +#define GADRTGBUTTON 2 + +struct Gadget RTGButton = +{ + &RTGStatus, 144, 19, button3w, button3h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[2], (APTR) &SharedBordersInvert[2], + &RTG_text, 0, NULL, GADRTGBUTTON, NULL +}; + +struct IntuiText AboutButton_text = +{ + 1, 0, JAM2, 8, 2, &font, (UBYTE *)"About", NULL +}; + +#define GADABOUT 1 + +struct Gadget AboutButton = +{ + &RTGButton, 356, 166, button1w, button1h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[0], (APTR) &SharedBordersInvert[0], + &AboutButton_text, 0, NULL, GADABOUT, NULL +}; + + +struct IntuiText QuitButton_text = +{ + 1, 0, JAM2, 12, 2, &font, (UBYTE *)"Quit", NULL +}; + +#define GADQUIT 0 + +struct Gadget QuitButton = +{ + &AboutButton, 438, 166, button1w, button1h, + GADGHIMAGE, + RELVERIFY, + BOOLGADGET, + (APTR) &SharedBorders[0], (APTR) &SharedBordersInvert[0], + &QuitButton_text, 0, NULL, GADQUIT, NULL +}; + + +struct NewWindow winlayout = +{ + 0, 0, + 512, 200, + -1, -1, + CLOSEWINDOW | GADGETUP | GADGETDOWN, + ACTIVATE | WINDOWCLOSE | WINDOWDRAG | WINDOWDEPTH, + &QuitButton, NULL, + (STRPTR)"PiStorm Interaction Tool", + NULL, NULL, + 0, 0, + 600, 400, + WBENCHSCREEN +}; + +// Pads what we are writing to screen with spaces, otherwise we get bits of +// old text still showing +static void WriteGadgetText(const char *text, UBYTE *buffer, struct Window *window, struct Gadget *gadget) +{ + ULONG newlen = strlen(text); + ULONG oldlen = strlen((char *)buffer); + + if (newlen < oldlen) + { + snprintf((char *)buffer, 64, "%s%*.*s", text, (int)(oldlen - newlen), + (int)(oldlen - newlen), " "); + } + else + { + strncpy((char *)buffer, text, 64); + } + + RefreshGadgets(gadget, window, NULL); +} +static void updateRTG(struct Window *window) +{ + unsigned short rtg = pi_get_rtg_status(); + if (rtg & 0x01) + { + WriteGadgetText("Disable RTG", RTG_buf, window, &RTGButton); + if (rtg & 0x02) + { + WriteGadgetText("RTG in use", RTGStatus_buf, window, &RTGStatus); + } + else + { + WriteGadgetText("RTG not in use", RTGStatus_buf, window, &RTGStatus); + } + } + else + { + WriteGadgetText("Enable RTG", RTG_buf, window, &RTGButton); + WriteGadgetText("RTG disabled", RTGStatus_buf, window, &RTGStatus); + } +} + +static char *GetSavePath() +{ + struct rtFileRequester *filereq; + char filename[128]; + char *fullpath = malloc(256 * sizeof(char)); + UBYTE *buf = NULL; + + if ((filereq = (struct rtFileRequester*)rtAllocRequestA (RT_FILEREQ, NULL))) + { + filename[0] = 0; + + if (!rtFileRequest(filereq, filename, "Pick a destination directory", + RTFI_Flags, FREQF_NOFILES, TAG_END)) + { + free(fullpath); + return NULL; + } + + } + else + { + rtEZRequest("Out of memory!", "Oh no!", NULL, NULL); + return NULL; + } + + strncpy(fullpath, (char*)filereq->Dir, 256); + rtFreeRequest((APTR)filereq); + return fullpath; +} + +int questionReboot() +{ + int res = rtEZRequest("This will restart the emulator, rebooting the Amiga\n" + "Continue anyway?", + "Yes|No", NULL, NULL); + return res; +} + +int main() +{ + struct Window *myWindow; + + IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 0); + if (IntuitionBase == NULL) + { + return RETURN_FAIL; + } + + if (!(ReqToolsBase = (struct ReqToolsBase *) + OpenLibrary (REQTOOLSNAME, REQTOOLSVERSION))) + { + static struct IntuiText pos; + struct IntuiText msg[] = + { + 1, 0, JAM2, 0, 0, &font, "You need reqtools.library V38 or higher!.", &msg[1], + 1, 0, JAM2, 0, 10, &font, "Please install it in your Libs: drirectory.", NULL, + }; + AutoRequest(NULL, msg, NULL, &pos, 0, 0, 0, 0); + return RETURN_FAIL; + } + + pistorm_base_addr = pi_find_pistorm(); + myWindow = OpenWindow(&winlayout); + BOOL no_board = FALSE; + + if (pistorm_base_addr == 0xFFFFFFFF) + { + rtEZRequest("Unable to find PiStorm autoconf device.", + "OK", NULL, NULL); + no_board = TRUE; + WriteGadgetText("PiStorm not found", StatusBar_buf, myWindow, &StatusBar); + } + else + { + WriteGadgetText("PiStorm found!", StatusBar_buf, myWindow, &StatusBar); + } + if (!no_board) + { + updateRTG(myWindow); + } + + FOREVER + { + BOOL closewin = FALSE; + struct IntuiMessage *message; + Wait(1 << myWindow->UserPort->mp_SigBit); + + while ((message = (struct IntuiMessage*)GetMsg(myWindow->UserPort))) + { + ULONG class = message->Class; + struct Gadget *address = (struct Gadget*)message->IAddress; + ReplyMsg((struct Message*)message); + + if (class == CLOSEWINDOW) + { + closewin = TRUE; + } + else if (class == GADGETUP) + { + if (no_board && (address->GadgetID != GADQUIT) && (address->GadgetID != GADABOUT)) + { + continue; + } + switch (address->GadgetID) + { + case GADQUIT: + closewin = TRUE; + break; + case GADABOUT: + { + static struct IntuiText pos; + char buf2[64], buf3[64]; + if (!no_board) + { + unsigned short hw_rev = pi_get_hw_rev(); + unsigned short sw_rev = pi_get_sw_rev(); + snprintf(buf2, 64, "PiStorm hardware: %d.%d", (hw_rev >> 8), (hw_rev & 0xFF)); + snprintf(buf3, 64, "PiStorm software: %d.%d", (sw_rev >> 8), (sw_rev & 0xFF)); + } + else + { + snprintf(buf2, 64, "PiStorm hardware not found!"); + } + rtEZRequest("PiStorm Interaction Tool %s\n" + "Tool written by beeanyew and LinuxJedi\n" + "%s\n%s\n\n" + "Now with 53%% more Nibbles!", + "More Nibbles!", NULL, NULL, VERSION, buf2, buf3); + break; + } + case GADRTGBUTTON: + { + unsigned short rtgStatus = pi_get_rtg_status() & 0x01; + pi_enable_rtg(!rtgStatus); + updateRTG(myWindow); + break; + } + case GADCONFIGCOMMIT: + { + if (!questionReboot()) + { + break; + } + Disable(); + unsigned short ret = pi_handle_config(PICFG_LOAD, ConfigFileValue_buf); + if (ret == PI_RES_FILENOTFOUND) + { + rtEZRequest("PiStorm says: \"file not found\"", + "OK", NULL, NULL); + } + break; + } + case GADCONFIGDEFAULT: + { + if (!questionReboot()) + { + break; + } + pi_handle_config(PICFG_DEFAULT, NULL); + break; + } + case GADRETRIEVEBUTTON: + { + unsigned int filesize = 0; + char outpath[128]; + unsigned char *buf; + + if (pi_get_filesize(GetFileValue_buf, &filesize) == PI_RES_FILENOTFOUND) + { + rtEZRequest("PiStorm says: \"file not found\"", + "OK", NULL, NULL); + break; + } + buf = malloc(filesize); + if (buf == NULL) + { + rtEZRequest("Could not allocate enough memory to transfer file", + "OK", NULL, NULL); + break; + } + WriteGadgetText("Retrieving file...", StatusBar_buf, myWindow, &StatusBar); + if (pi_transfer_file(GetFileValue_buf, buf) != PI_RES_OK) + { + rtEZRequest("PiStorm says: \"something went wrong with the file transfer\"", + "OK", NULL, NULL); + WriteGadgetText("File transfer failed", StatusBar_buf, myWindow, &StatusBar); + free(buf); + break; + } + char *fname = strrchr(GetFileValue_buf, '/'); + if (!fname) + { + fname = GetFileValue_buf; + } + char *destfile = malloc(256); + // Turns out WB doesn't like DF0:/filename.ext + if (DestinationValue_buf[(strlen(DestinationValue_buf) - 1)] == ':') + { + snprintf(destfile, 255, "%s%s", DestinationValue_buf, GetFileValue_buf); + } + else if (!strlen(DestinationValue_buf)) + { + snprintf(destfile, 255, "%s", GetFileValue_buf); + } + else + { + snprintf(destfile, 255, "%s/%s", DestinationValue_buf, GetFileValue_buf); + } + BPTR fh = Open(destfile, MODE_NEWFILE); + if (!fh) + { + char errbuf[64]; + snprintf(errbuf, 64, "Error code: %ld", IoErr()); + rtEZRequest("Could not open file for writing\n" + "%s\n%s", + "OK", NULL, NULL, destfile, errbuf); + WriteGadgetText("File transfer failed", StatusBar_buf, myWindow, &StatusBar); + free(buf); + free(destfile); + break; + } + Write(fh, buf, filesize); + Close(fh); + free(destfile); + WriteGadgetText("File transfer complete", StatusBar_buf, myWindow, &StatusBar); + free(buf); + break; + } + case GADREBOOT: + { + WriteGadgetText("Rebooting Amiga", StatusBar_buf, myWindow, &StatusBar); + pi_reset_amiga(0); + break; + } + case GADGETDESTINATION: + { + char *fileName = GetSavePath(); + if (fileName) + { + WriteGadgetText(fileName, DestinationValue_buf, myWindow, &GetDestination); + free(fileName); + } + break; + } + case GADSHUTDOWN: + { + int res = rtEZRequest("This will shutdown the Pi and cause the Amiga to freeze\n" + "Continue anyway?", + "Yes|No", NULL, NULL); + if (!res) + { + break; + } + WriteGadgetText("Shuttting down PiStorm...", StatusBar_buf, myWindow, &StatusBar); + int confirm = pi_shutdown_pi(0); + pi_confirm_shutdown(confirm); + break; + } + case GADKICKSTARTCOMMIT: + { + if (!questionReboot()) + { + break; + } + Disable(); + unsigned short ret = pi_remap_kickrom(KickstartFileValue_buf); + if (ret == PI_RES_FILENOTFOUND) + { + rtEZRequest("PiStorm says: \"file not found\"", + "OK", NULL, NULL); + } + break; + } + } + } + if (closewin) + { + break; + } + } + if (closewin) + { + break; + } + }; + if (myWindow) CloseWindow(myWindow); + CloseLibrary((struct Library*)ReqToolsBase); + return 0; +} diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs13.info b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs13.info new file mode 100644 index 0000000..03c62f0 Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs13.info differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs13/reqtools.library b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs13/reqtools.library new file mode 100644 index 0000000..2df07fc Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs13/reqtools.library differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs20.info b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs20.info new file mode 100644 index 0000000..935dee8 Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs20.info differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs20/reqtools.library b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs20/reqtools.library new file mode 100644 index 0000000..224074d Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/libs20/reqtools.library differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/pistorm_dev.c b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/pistorm_dev.c new file mode 100644 index 0000000..d476b5f --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/pistorm_dev.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT + +#include "../pistorm-dev-enums.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef HAS_STDLIB +#include +#endif + +unsigned int pistorm_base_addr = 0xFFFFFFFF; + +#define WRITESHORT(cmd, val) *(unsigned short *)((unsigned int)(pistorm_base_addr+cmd)) = val; +#define WRITELONG(cmd, val) *(unsigned int *)((unsigned int)(pistorm_base_addr+cmd)) = val; +#define WRITEBYTE(cmd, val) *(unsigned char *)((unsigned int)(pistorm_base_addr+cmd)) = val; + +#define READSHORT(cmd, var) var = *(volatile unsigned short *)(pistorm_base_addr + cmd); +#define READLONG(cmd, var) var = *(volatile unsigned int *)(pistorm_base_addr + cmd); +#define READBYTE(cmd, var) var = *(volatile unsigned short *)(pistorm_base_addr + cmd); + +#define RETURN_CMDRES READSHORT(PI_CMDRESULT, short_val); return short_val; + +unsigned short short_val; +unsigned int long_val; + +unsigned int pi_find_pistorm(void) { + unsigned int board_addr = 0xFFFFFFFF; + struct ExpansionBase *expansionbase = (struct ExpansionBase *)OpenLibrary((STRPTR)"expansion.library", 0L); + + if (expansionbase == NULL) { +#ifdef HAS_STDLIB + printf("Failed to open expansion.library.\n"); +#endif + } + else { + struct ConfigDev* cd = NULL; + cd = (struct ConfigDev*)FindConfigDev(cd, 2011, 0x6B); + if (cd != NULL) + board_addr = (unsigned int)cd->cd_BoardAddr; + CloseLibrary((struct Library *)expansionbase); + } + pistorm_base_addr = board_addr; + return board_addr; +} + +void pi_reset_amiga(unsigned short reset_code) { + WRITESHORT(PI_CMD_RESET, reset_code); +} + +unsigned short pi_shutdown_pi(unsigned short shutdown_code) { + WRITESHORT(PI_CMD_SHUTDOWN, shutdown_code); + + RETURN_CMDRES; +} + +unsigned short pi_confirm_shutdown(unsigned short shutdown_code) { + WRITESHORT(PI_CMD_CONFIRMSHUTDOWN, shutdown_code); + + RETURN_CMDRES; +} + +// Kickstart/Extended ROM stuff +unsigned short pi_remap_kickrom(char *filename) { + WRITELONG(PI_STR1, (unsigned int)filename); + WRITESHORT(PI_CMD_KICKROM, 1); + + RETURN_CMDRES; +} + +unsigned short pi_remap_extrom(char *filename) { + WRITELONG(PI_STR1, (unsigned int)filename); + WRITESHORT(PI_CMD_EXTROM, 1); + + RETURN_CMDRES; +} + +// File operation things +unsigned short pi_get_filesize(char *filename, unsigned int *file_size) { + WRITELONG(PI_STR1, (unsigned int)filename); + READLONG(PI_CMD_FILESIZE, *file_size); + + RETURN_CMDRES; +} + +unsigned short pi_transfer_file(char *filename, unsigned char *dest_ptr) { + WRITELONG(PI_STR1, (unsigned int)filename); + WRITELONG(PI_PTR1, (unsigned int)dest_ptr); + WRITESHORT(PI_CMD_TRANSFERFILE, 1); + + RETURN_CMDRES; +} + +unsigned short pi_memcpy(unsigned char *dst, unsigned char *src, unsigned int size) { + WRITELONG(PI_PTR1, (unsigned int)src); + WRITELONG(PI_PTR2, (unsigned int)dst); + WRITELONG(PI_CMD_MEMCPY, size); + + RETURN_CMDRES; +} + +unsigned short pi_memset(unsigned char *dst, unsigned char val, unsigned int size) { + WRITELONG(PI_PTR1, (unsigned int)dst); + WRITEBYTE(PI_BYTE1, val); + WRITELONG(PI_CMD_MEMSET, size); + + RETURN_CMDRES; +} + +// Generic memory copyrect, assuming that the src/dst offsets are already adjusted for X/Y coordinates. +void pi_copyrect(unsigned char *dst, unsigned char *src, + unsigned short src_pitch, unsigned short dst_pitch, + unsigned short w, unsigned short h) { + WRITELONG(PI_PTR1, (unsigned int)src); + WRITELONG(PI_PTR2, (unsigned int)dst); + WRITESHORT(PI_WORD1, src_pitch); + WRITESHORT(PI_WORD2, dst_pitch); + WRITESHORT(PI_WORD3, w); + WRITESHORT(PI_WORD4, h); + + WRITESHORT(PI_CMD_COPYRECT, 1); +} + +// Extended memory copyrect, allowing specifying of source/dest X/Y coordinates. +void pi_copyrect_ex(unsigned char *dst, unsigned char *src, + unsigned short src_pitch, unsigned short dst_pitch, + unsigned short src_x, unsigned short src_y, + unsigned short dst_x, unsigned short dst_y, + unsigned short w, unsigned short h) { + WRITELONG(PI_PTR1, (unsigned int)src); + WRITELONG(PI_PTR2, (unsigned int)dst); + WRITESHORT(PI_WORD1, src_pitch); + WRITESHORT(PI_WORD2, dst_pitch); + WRITESHORT(PI_WORD3, w); + WRITESHORT(PI_WORD4, h); + + WRITESHORT(PI_WORD5, src_x); + WRITESHORT(PI_WORD6, src_y); + WRITESHORT(PI_WORD7, dst_x); + WRITESHORT(PI_WORD8, dst_y); + + WRITESHORT(PI_CMD_COPYRECT_EX, 1); +} + +// PiSCSI stuff +// TODO: There's currently no way to read back what drives are mounted at which SCSI index. +unsigned short pi_piscsi_map_drive(char *filename, unsigned char index) { + WRITESHORT(PI_WORD1, index); + WRITELONG(PI_STR1, (unsigned int)filename); + WRITESHORT(PI_CMD_PISCSI_CTRL, PISCSI_CTRL_MAP); + + RETURN_CMDRES; +} + +unsigned short pi_piscsi_unmap_drive(unsigned char index) { + WRITESHORT(PI_WORD1, index); + WRITESHORT(PI_CMD_PISCSI_CTRL, PISCSI_CTRL_UNMAP); + + RETURN_CMDRES; +} + +// For virtual removable media. Not yet implemented. +unsigned short pi_piscsi_insert_media(char *filename, unsigned char index) { + WRITESHORT(PI_WORD1, index); + WRITELONG(PI_STR1, (unsigned int)filename); + WRITESHORT(PI_CMD_PISCSI_CTRL, PISCSI_CTRL_INSERT); + + RETURN_CMDRES; +} + +unsigned short pi_piscsi_eject_media(unsigned char index) { + WRITESHORT(PI_WORD1, index); + WRITESHORT(PI_CMD_PISCSI_CTRL, PISCSI_CTRL_EJECT); + + RETURN_CMDRES; +} + +// Config file stuff +unsigned short pi_load_config(char *filename) { + WRITELONG(PI_STR1, (unsigned int)filename); + WRITESHORT(PI_CMD_SWITCHCONFIG, PICFG_LOAD); + + RETURN_CMDRES; +} + +void pi_reload_config(void) { + WRITESHORT(PI_CMD_SWITCHCONFIG, PICFG_RELOAD); +} + +void pi_load_default_config(void) { + WRITESHORT(PI_CMD_SWITCHCONFIG, PICFG_DEFAULT); +} + +unsigned short pi_handle_config(unsigned char cmd, char *str) { + if (cmd == PICFG_LOAD) { + WRITELONG(PI_STR1, (unsigned int)str); + } + WRITESHORT(PI_CMD_SWITCHCONFIG, cmd); + + RETURN_CMDRES; +} + +// Simple feature status write functions +void pi_enable_rtg(unsigned short val) +{ + WRITESHORT(PI_CMD_RTGSTATUS, val); +} +void pi_enable_net(unsigned short val) +{ + WRITESHORT(PI_CMD_NETSTATUS, val); +} +void pi_enable_piscsi(unsigned short val) +{ + WRITESHORT(PI_CMD_PISCSI_CTRL, val); +} + +// Generic feature status setting function. +// Example: pi_set_feature_status(PI_CMD_RTGSTATUS, 1) to enable RTG +// pi_set_feature_status(PI_CMD_PISCSI_CTRL, PISCSI_CTRL_ENABLE) to enable PiSCSI +void pi_set_feature_status(unsigned short cmd, unsigned char value) { + WRITESHORT(cmd, value); +} + +#define SIMPLEREAD_SHORT(a, b) \ + unsigned short a(void) { READSHORT(b, short_val); return short_val; } + +unsigned int pi_get_fb(void) { + READLONG(PI_CMD_GET_FB, long_val); + return long_val; +} + +// Simple feature status read functions +unsigned short pi_get_hw_rev(void) +{ + READSHORT(PI_CMD_HWREV, short_val); + return short_val; +} +unsigned short pi_get_sw_rev(void) +{ + READSHORT(PI_CMD_SWREV, short_val); + return short_val; +} +unsigned short pi_get_rtg_status(void) +{ + READSHORT(PI_CMD_RTGSTATUS, short_val); + return short_val; +} +unsigned short pi_get_net_status(void) +{ + READSHORT(PI_CMD_NETSTATUS, short_val); + return short_val; +} +unsigned short pi_get_piscsi_status(void) +{ + READSHORT(PI_CMD_PISCSI_CTRL, short_val); + return short_val; +} +unsigned short pi_get_cmd_result(void) +{ + READSHORT(PI_CMDRESULT, short_val); + return short_val; +} diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/pistorm_dev.h b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/pistorm_dev.h new file mode 100644 index 0000000..0cc08bd --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/pistorm_dev.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +unsigned int pi_find_pistorm(void); + +unsigned short pi_get_hw_rev(void); +unsigned short pi_get_sw_rev(void); +unsigned short pi_get_net_status(void); +unsigned short pi_get_rtg_status(void); +unsigned short pi_get_piscsi_status(void); + +void pi_enable_rtg(unsigned short val); +void pi_enable_net(unsigned short val); +void pi_enable_piscsi(unsigned short val); + +void pi_reset_amiga(unsigned short reset_code); +unsigned short pi_handle_config(unsigned char cmd, char *str); + +void pi_set_feature_status(unsigned short cmd, unsigned char value); + +unsigned short pi_piscsi_map_drive(char *filename, unsigned char index); +unsigned short pi_piscsi_unmap_drive(unsigned char index); +unsigned short pi_piscsi_insert_media(char *filename, unsigned char index); +unsigned short pi_piscsi_eject_media(unsigned char index); + +unsigned short pi_get_filesize(char *filename, unsigned int *file_size); +unsigned short pi_transfer_file(char *filename, unsigned char *dest_ptr); +unsigned short pi_memcpy(unsigned char *dst, unsigned char *src, unsigned int size); +unsigned short pi_memset(unsigned char *dst, unsigned char val, unsigned int size); +void pi_copyrect(unsigned char *dst, unsigned char *src, unsigned short src_pitch, unsigned short dst_pitch, unsigned short w, unsigned short h); +void pi_copyrect_ex(unsigned char *dst, unsigned char *src, unsigned short src_pitch, unsigned short dst_pitch, unsigned short src_x, unsigned short src_y, unsigned short dst_x, unsigned short dst_y, unsigned short w, unsigned short h); +unsigned int pi_get_fb(void); + +unsigned short pi_load_config(char *filename); +void pi_reload_config(void); +void pi_load_default_config(void); + +unsigned short pi_remap_kickrom(char *filename); +unsigned short pi_remap_extrom(char *filename); + +unsigned short pi_shutdown_pi(unsigned short shutdown_code); +unsigned short pi_confirm_shutdown(unsigned short shutdown_code); diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/clib/reqtools_protos.h b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/clib/reqtools_protos.h new file mode 100644 index 0000000..9d2e537 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/clib/reqtools_protos.h @@ -0,0 +1,61 @@ +#ifndef CLIB_REQTOOLS_PROTOS_H +#define CLIB_REQTOOLS_PROTOS_H +/* +** $Filename: clib/reqtools_protos.h $ +** $Release: 2.2 $ +** $Revision: 38.11 $ +** +** C prototypes. For use with 32 bit integers only. +** +** (C) Copyright 1991-1994 Nico François +** All Rights Reserved +*/ + +#ifndef UTILITY_TAGITEM_H +#include +#endif /* UTILITY_TAGITEM_H */ + +APTR rtAllocRequestA (ULONG, struct TagItem *); +void rtFreeRequest (APTR); +void rtFreeReqBuffer (APTR); +LONG rtChangeReqAttrA (APTR, struct TagItem *); +APTR rtFileRequestA(struct rtFileRequester *,char *,char *,struct TagItem *); +void rtFreeFileList (struct rtFileList *); +ULONG rtEZRequestA (char *,char *,struct rtReqInfo *,APTR,struct TagItem *); +ULONG rtGetStringA (UBYTE *,ULONG,char *,struct rtReqInfo *,struct TagItem *); +ULONG rtGetLongA (ULONG *, char *, struct rtReqInfo *, struct TagItem *); +ULONG rtFontRequestA (struct rtFontRequester *, char *, struct TagItem *); +LONG rtPaletteRequestA (char *, struct rtReqInfo *, struct TagItem *); +ULONG rtReqHandlerA (struct rtHandlerInfo *, ULONG, struct TagItem *); +void rtSetWaitPointer (struct Window *); +ULONG rtGetVScreenSize (struct Screen *, ULONG *, ULONG *); +void rtSetReqPosition (ULONG, struct NewWindow *, + struct Screen *, struct Window *); +void rtSpread (ULONG *, ULONG *, ULONG, ULONG, ULONG, ULONG); +void rtScreenToFrontSafely (struct Screen *); +ULONG rtScreenModeRequestA (struct rtScreenModeRequester *, + char *, struct TagItem *); +void rtCloseWindowSafely (struct Window *); +APTR rtLockWindow (struct Window *); +void rtUnlockWindow (struct Window *, APTR); + +/* private functions */ + +struct ReqToolsPrefs *rtLockPrefs (void); +void rtUnlockPrefs (void); + +/* functions with varargs in reqtools.lib and reqtoolsnb.lib */ + +APTR rtAllocRequest (ULONG, Tag,...); +LONG rtChangeReqAttr (APTR, Tag,...); +APTR rtFileRequest (struct rtFileRequester *, char *, char *, Tag,...); +ULONG rtEZRequest (char *, char *, struct rtReqInfo *, struct TagItem *,...); +ULONG rtEZRequestTags (char *, char *, struct rtReqInfo *, APTR, Tag,...); +ULONG rtGetString (UBYTE *, ULONG, char *, struct rtReqInfo *, Tag,...); +ULONG rtGetLong (ULONG *, char *, struct rtReqInfo *, Tag,...); +ULONG rtFontRequest (struct rtFontRequester *, char *, Tag,...); +LONG rtPaletteRequest (char *, struct rtReqInfo *, Tag,...); +ULONG rtReqHandler (struct rtHandlerInfo *, ULONG, Tag,...); +ULONG rtScreenModeRequest (struct rtScreenModeRequester *, char *, Tag,...); + +#endif /* CLIB_REQTOOLS_PROTOS_H */ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/libraries/reqtools.h b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/libraries/reqtools.h new file mode 100644 index 0000000..98982d8 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/libraries/reqtools.h @@ -0,0 +1,635 @@ +#ifndef LIBRARIES_REQTOOLS_H +#define LIBRARIES_REQTOOLS_H +/* +** $Filename: libraries/reqtools.h $ +** $Release: 2.2 $ +** $Revision: 38.11 $ +** +** reqtools.library definitions +** +** (C) Copyright 1991-1994 Nico François +** All Rights Reserved +*/ + +#ifndef EXEC_TYPES_H +#include +#endif /* EXEC_TYPES_H */ + +#ifndef EXEC_LISTS_H +#include +#endif /* EXEC_LISTS_H */ + +#ifndef EXEC_LIBRARIES_H +#include +#endif /* EXEC_LIBRARIES_H */ + +#ifndef EXEC_SEMAPHORES_H +#include +#endif /* EXEC_SEMAPHORES_H */ + +#ifndef LIBRARIES_DOS_H +#include +#endif /* LIBRARIES_DOS_H */ + +#ifndef LIBRARIES_DOSEXTENS_H +#include +#endif /* LIBRARIES_DOSEXTENS_H */ + +#ifndef LIBRARIES_DISKFONT_H +#include +#endif /* LIBRARIES_DISKFONT_H */ + +#ifndef GRAPHICS_TEXT_H +#include +#endif /* GRAPHICS_TEXT_H */ + +#ifndef UTILITY_TAGITEM_H +#include +#endif /* UTILITY_TAGITEM_H */ + +#define REQTOOLSNAME "reqtools.library" +#define REQTOOLSVERSION 38L + +/*********************** +* * +* Preferences * +* * +***********************/ + +#define RTPREF_FILEREQ 0L +#define RTPREF_FONTREQ 1L +#define RTPREF_PALETTEREQ 2L +#define RTPREF_SCREENMODEREQ 3L +#define RTPREF_VOLUMEREQ 4L +#define RTPREF_OTHERREQ 5L +#define RTPREF_NR_OF_REQ 6L + +struct ReqDefaults { + ULONG Size; + ULONG ReqPos; + UWORD LeftOffset; + UWORD TopOffset; + UWORD MinEntries; + UWORD MaxEntries; + }; + +struct ReqToolsPrefs { + /* Size of preferences (_without_ this field and the semaphore) */ + ULONG PrefsSize; + struct SignalSemaphore PrefsSemaphore; + /* Start of real preferences */ + ULONG Flags; + struct ReqDefaults ReqDefaults[RTPREF_NR_OF_REQ]; + }; + +#define RTPREFS_SIZE \ + (sizeof (struct ReqToolsPrefs) - sizeof (struct SignalSemaphore) - 4) + +/* Flags */ + +#define RTPRB_DIRSFIRST 0L +#define RTPRF_DIRSFIRST (1L<DefaultFont */ +#define RT_DefaultFont (RT_TagBase+9) +/* boolean to set the standard wait pointer in window - default FALSE */ +#define RT_WaitPointer (RT_TagBase+10) +/* (V38) char preceding keyboard shortcut characters (will be underlined) */ +#define RT_Underscore (RT_TagBase+11) +/* (V38) share IDCMP port with window - default FALSE */ +#define RT_ShareIDCMP (RT_TagBase+12) +/* (V38) lock window and set standard wait pointer - default FALSE */ +#define RT_LockWindow (RT_TagBase+13) +/* (V38) boolean to make requester's screen pop to front - default TRUE */ +#define RT_ScreenToFront (RT_TagBase+14) +/* (V38) Requester should use this font - default: screen font */ +#define RT_TextAttr (RT_TagBase+15) +/* (V38) call this hook for every IDCMP message not for requester */ +#define RT_IntuiMsgFunc (RT_TagBase+16) +/* (V38) Locale ReqTools should use for text */ +#define RT_Locale (RT_TagBase+17) + +/*** tags specific to rtEZRequestA *** +*/ +/* title of requester window - english default "Request" or "Information" */ +#define RTEZ_ReqTitle (RT_TagBase+20) +/* (RT_TagBase+21) reserved */ +/* various flags (see below) */ +#define RTEZ_Flags (RT_TagBase+22) +/* default response (activated by pressing RETURN) - default TRUE */ +#define RTEZ_DefaultResponse (RT_TagBase+23) + +/*** tags specific to rtGetLongA *** +*/ +/* minimum allowed value - default MININT */ +#define RTGL_Min (RT_TagBase+30) +/* maximum allowed value - default MAXINT */ +#define RTGL_Max (RT_TagBase+31) +/* suggested width of requester window (in pixels) */ +#define RTGL_Width (RT_TagBase+32) +/* boolean to show the default value - default TRUE */ +#define RTGL_ShowDefault (RT_TagBase+33) +/* (V38) string with possible responses - english default " _Ok |_Cancel" */ +#define RTGL_GadFmt (RT_TagBase+34) +/* (V38) optional arguments for RTGL_GadFmt */ +#define RTGL_GadFmtArgs (RT_TagBase+35) +/* (V38) invisible typing - default FALSE */ +#define RTGL_Invisible (RT_TagBase+36) +/* (V38) window backfill - default TRUE */ +#define RTGL_BackFill (RT_TagBase+37) +/* (V38) optional text above gadget */ +#define RTGL_TextFmt (RT_TagBase+38) +/* (V38) optional arguments for RTGS_TextFmt */ +#define RTGL_TextFmtArgs (RT_TagBase+39) +/* (V38) various flags (see below) */ +#define RTGL_Flags RTEZ_Flags + +/*** tags specific to rtGetStringA *** +*/ +/* suggested width of requester window (in pixels) */ +#define RTGS_Width RTGL_Width +/* allow empty string to be accepted - default FALSE */ +#define RTGS_AllowEmpty (RT_TagBase+80) +/* (V38) string with possible responses - english default " _Ok |_Cancel" */ +#define RTGS_GadFmt RTGL_GadFmt +/* (V38) optional arguments for RTGS_GadFmt */ +#define RTGS_GadFmtArgs RTGL_GadFmtArgs +/* (V38) invisible typing - default FALSE */ +#define RTGS_Invisible RTGL_Invisible +/* (V38) window backfill - default TRUE */ +#define RTGS_BackFill RTGL_BackFill +/* (V38) optional text above gadget */ +#define RTGS_TextFmt RTGL_TextFmt +/* (V38) optional arguments for RTGS_TextFmt */ +#define RTGS_TextFmtArgs RTGL_TextFmtArgs +/* (V38) various flags (see below) */ +#define RTGS_Flags RTEZ_Flags + +/*** tags specific to rtFileRequestA *** +*/ +/* various flags (see below) */ +#define RTFI_Flags (RT_TagBase+40) +/* suggested height of file requester */ +#define RTFI_Height (RT_TagBase+41) +/* replacement text for 'Ok' gadget (max 6 chars) */ +#define RTFI_OkText (RT_TagBase+42) +/* (V38) bring up volume requester, tag data holds flags (see below) */ +#define RTFI_VolumeRequest (RT_TagBase+43) +/* (V38) call this hook for every file in the directory */ +#define RTFI_FilterFunc (RT_TagBase+44) +/* (V38) allow empty file to be accepted - default FALSE */ +#define RTFI_AllowEmpty (RT_TagBase+45) + +/*** tags specific to rtFontRequestA *** +*/ +/* various flags (see below) */ +#define RTFO_Flags RTFI_Flags +/* suggested height of font requester */ +#define RTFO_Height RTFI_Height +/* replacement text for 'Ok' gadget (max 6 chars) */ +#define RTFO_OkText RTFI_OkText +/* suggested height of font sample display - default 24 */ +#define RTFO_SampleHeight (RT_TagBase+60) +/* minimum height of font displayed */ +#define RTFO_MinHeight (RT_TagBase+61) +/* maximum height of font displayed */ +#define RTFO_MaxHeight (RT_TagBase+62) +/* [(RT_TagBase+63) to (RT_TagBase+66) used below] */ +/* (V38) call this hook for every font */ +#define RTFO_FilterFunc RTFI_FilterFunc + +/*** (V38) tags for rtScreenModeRequestA *** +*/ +/* various flags (see below) */ +#define RTSC_Flags RTFI_Flags +/* suggested height of screenmode requester */ +#define RTSC_Height RTFI_Height +/* replacement text for 'Ok' gadget (max 6 chars) */ +#define RTSC_OkText RTFI_OkText +/* property flags (see also RTSC_PropertyMask) */ +#define RTSC_PropertyFlags (RT_TagBase+90) +/* property mask - default all bits in RTSC_PropertyFlags considered */ +#define RTSC_PropertyMask (RT_TagBase+91) +/* minimum display width allowed */ +#define RTSC_MinWidth (RT_TagBase+92) +/* maximum display width allowed */ +#define RTSC_MaxWidth (RT_TagBase+93) +/* minimum display height allowed */ +#define RTSC_MinHeight (RT_TagBase+94) +/* maximum display height allowed */ +#define RTSC_MaxHeight (RT_TagBase+95) +/* minimum display depth allowed */ +#define RTSC_MinDepth (RT_TagBase+96) +/* maximum display depth allowed */ +#define RTSC_MaxDepth (RT_TagBase+97) +/* call this hook for every display mode id */ +#define RTSC_FilterFunc RTFI_FilterFunc + +/*** tags for rtChangeReqAttrA *** +*/ +/* file requester - set directory */ +#define RTFI_Dir (RT_TagBase+50) +/* file requester - set wildcard pattern */ +#define RTFI_MatchPat (RT_TagBase+51) +/* file requester - add a file or directory to the buffer */ +#define RTFI_AddEntry (RT_TagBase+52) +/* file requester - remove a file or directory from the buffer */ +#define RTFI_RemoveEntry (RT_TagBase+53) +/* font requester - set font name of selected font */ +#define RTFO_FontName (RT_TagBase+63) +/* font requester - set font size */ +#define RTFO_FontHeight (RT_TagBase+64) +/* font requester - set font style */ +#define RTFO_FontStyle (RT_TagBase+65) +/* font requester - set font flags */ +#define RTFO_FontFlags (RT_TagBase+66) +/* (V38) screenmode requester - get display attributes from screen */ +#define RTSC_ModeFromScreen (RT_TagBase+80) +/* (V38) screenmode requester - set display mode id (32-bit extended) */ +#define RTSC_DisplayID (RT_TagBase+81) +/* (V38) screenmode requester - set display width */ +#define RTSC_DisplayWidth (RT_TagBase+82) +/* (V38) screenmode requester - set display height */ +#define RTSC_DisplayHeight (RT_TagBase+83) +/* (V38) screenmode requester - set display depth */ +#define RTSC_DisplayDepth (RT_TagBase+84) +/* (V38) screenmode requester - set overscan type, 0 for regular size */ +#define RTSC_OverscanType (RT_TagBase+85) +/* (V38) screenmode requester - set autoscroll */ +#define RTSC_AutoScroll (RT_TagBase+86) + +/*** tags for rtPaletteRequestA *** +*/ +/* initially selected color - default 1 */ +#define RTPA_Color (RT_TagBase+70) + +/*** tags for rtReqHandlerA *** +*/ +/* end requester by software control, set tagdata to REQ_CANCEL, REQ_OK or + in case of rtEZRequest to the return value */ +#define RTRH_EndRequest (RT_TagBase+60) + +/*** tags for rtAllocRequestA ***/ +/* no tags defined yet */ + + +/************ +* RT_ReqPos * +************/ +#define REQPOS_POINTER 0L +#define REQPOS_CENTERWIN 1L +#define REQPOS_CENTERSCR 2L +#define REQPOS_TOPLEFTWIN 3L +#define REQPOS_TOPLEFTSCR 4L + +/****************** +* RTRH_EndRequest * +******************/ +#define REQ_CANCEL 0L +#define REQ_OK 1L + +/*************************************** +* flags for RTFI_Flags and RTFO_Flags * +* or filereq->Flags and fontreq->Flags * +***************************************/ +#define FREQB_NOBUFFER 2L +#define FREQF_NOBUFFER (1L<Flags * +*****************************************/ +#define FREQB_MULTISELECT 0L +#define FREQF_MULTISELECT (1L<Flags * +*****************************************/ +#define FREQB_FIXEDWIDTH 5L +#define FREQF_FIXEDWIDTH (1L<Flags * +*****************************************************/ +#define SCREQB_SIZEGADS 13L +#define SCREQF_SIZEGADS (1L<Flags * +*****************************************/ +#define EZREQB_NORETURNKEY 0L +#define EZREQF_NORETURNKEY (1L<Flags * +***********************************************/ +#define GLREQB_CENTERTEXT EZREQB_CENTERTEXT +#define GLREQF_CENTERTEXT EZREQF_CENTERTEXT +#define GLREQB_HIGHLIGHTTEXT 3L +#define GLREQF_HIGHLIGHTTEXT (1L<Flags * +***********************************************/ +#define GSREQB_CENTERTEXT EZREQB_CENTERTEXT +#define GSREQF_CENTERTEXT EZREQF_CENTERTEXT +#define GSREQB_HIGHLIGHTTEXT GLREQB_HIGHLIGHTTEXT +#define GSREQF_HIGHLIGHTTEXT GLREQF_HIGHLIGHTTEXT + +/***************************************** +* (V38) flags for RTFI_VolumeRequest tag * +*****************************************/ +#define VREQB_NOASSIGNS 0L +#define VREQF_NOASSIGNS (1L<DefaultFont +RT_DefaultFont equ (RT_TagBase+9) +* boolean to set the standard wait pointer in window - default FALSE +RT_WaitPointer equ (RT_TagBase+10) +* (V38) char preceding keyboard shortcut characters (will be underlined) +RT_Underscore equ (RT_TagBase+11) +* (V38) share IDCMP port with window - default FALSE +RT_ShareIDCMP equ (RT_TagBase+12) +* (V38) lock window and set standard wait pointer - default FALSE +RT_LockWindow equ (RT_TagBase+13) +* (V38) boolean to make requester's screen pop to front - default TRUE +RT_ScreenToFront equ (RT_TagBase+14) +* (V38) Requester should use this font - default: screen font +RT_TextAttr equ (RT_TagBase+15) +* (V38) call this hook for every IDCMP message not for requester +RT_IntuiMsgFunc equ (RT_TagBase+16) +* (V38) Locale ReqTools should use for text +RT_Locale equ (RT_TagBase+17) + +*** tags specific to rtEZRequestA *** +* +* title of requester window - english default "Request" or "Information" +RTEZ_ReqTitle equ (RT_TagBase+20) +* (RT_TagBase+21) reserved +* various flags (see below) +RTEZ_Flags equ (RT_TagBase+22) +* default response (activated by pressing RETURN) - default TRUE +RTEZ_DefaultResponse equ (RT_TagBase+23) + +*** tags specific to rtGetLongA *** +* +* minimum allowed value - default MININT +RTGL_Min equ (RT_TagBase+30) +* maximum allowed value - default MAXINT +RTGL_Max equ (RT_TagBase+31) +* suggested width of requester window (in pixels) +RTGL_Width equ (RT_TagBase+32) +* boolean to show the default value - default TRUE +RTGL_ShowDefault equ (RT_TagBase+33) +* (V38) string with possible responses - english default " _Ok |_Cancel" +RTGL_GadFmt equ (RT_TagBase+34) +* (V38) optional arguments for RTGL_GadFmt +RTGL_GadFmtArgs equ (RT_TagBase+35) +* (V38) invisible typing - default FALSE +RTGL_Invisible equ (RT_TagBase+36) +* (V38) window backfill - default TRUE +RTGL_BackFill equ (RT_TagBase+37) +* (V38) optional text above gadget +RTGL_TextFmt equ (RT_TagBase+38) +* (V38) optional arguments for RTGS_TextFmt +RTGL_TextFmtArgs equ (RT_TagBase+39) +* (V38) Center text - default FALSE +RTGL_CenterText equ (RT_TagBase+100) +* (V38) various flags (see below) +RTGL_Flags equ RTEZ_Flags + +*** tags specific to rtGetStringA *** +* +* suggested width of requester window (in pixels) +RTGS_Width equ RTGL_Width +* allow empty string to be accepted - default FALSE +RTGS_AllowEmpty equ (RT_TagBase+80) +* (V38) string with possible responses - english default " _Ok |_Cancel" +RTGS_GadFmt equ RTGL_GadFmt +* (V38) optional arguments for RTGS_GadFmt +RTGS_GadFmtArgs equ RTGL_GadFmtArgs +* (V38) invisible typing - default FALSE +RTGS_Invisible equ RTGL_Invisible +* (V38) window backfill - default TRUE +RTGS_BackFill equ RTGL_BackFill +* (V38) optional text above gadget +RTGS_TextFmt equ RTGL_TextFmt +* (V38) optional arguments for RTGS_TextFmt +RTGS_TextFmtArgs equ RTGL_TextFmtArgs +* (V38) Center text - default FALSE +RTGS_CenterText equ RTGL_CenterText +* (V38) various flags (see below) +RTGS_Flags equ RTEZ_Flags + +*** tags specific to rtFileRequestA *** +* +* various flags (see below) +RTFI_Flags equ (RT_TagBase+40) +* suggested height of file requester +RTFI_Height equ (RT_TagBase+41) +* replacement text for 'Ok' gadget (max 6 chars) +RTFI_OkText equ (RT_TagBase+42) +* (V38) bring up volume requester, tag data holds flags (see below) +RTFI_VolumeRequest equ (RT_TagBase+43) +* (V38) call this hook for every file in the directory +RTFI_FilterFunc equ (RT_TagBase+44) +* (V38) allow empty file to be accepted - default FALSE +RTFI_AllowEmpty equ (RT_TagBase+45) + +*** tags specific to rtFontRequestA *** +* +* various flags (see below) +RTFO_Flags equ RTFI_Flags +* suggested height of font requester +RTFO_Height equ RTFI_Height +* replacement text for 'Ok' gadget (max 6 chars) +RTFO_OkText equ RTFI_OkText +* suggested height of font sample display - default 24 +RTFO_SampleHeight equ (RT_TagBase+60) +* minimum height of font displayed +RTFO_MinHeight equ (RT_TagBase+61) +* maximum height of font displayed +RTFO_MaxHeight equ (RT_TagBase+62) +* [(RT_TagBase+63) to (RT_TagBase+66) used below] +* (V38) call this hook for every font +RTFO_FilterFunc equ RTFI_FilterFunc + +*** (V38) tags for rtScreenModeRequestA *** +* various flags (see below) +RTSC_Flags equ RTFI_Flags +* suggested height of screenmode requester +RTSC_Height equ RTFI_Height +* replacement text for 'Ok' gadget (max 6 chars) +RTSC_OkText equ RTFI_OkText +* property flags (see also RTSC_PropertyMask) +RTSC_PropertyFlags equ (RT_TagBase+90) +* property mask - default all bits in RTSC_PropertyFlags considered +RTSC_PropertyMask equ (RT_TagBase+91) +* minimum display width allowed +RTSC_MinWidth equ (RT_TagBase+92) +* maximum display width allowed +RTSC_MaxWidth equ (RT_TagBase+93) +* minimum display height allowed +RTSC_MinHeight equ (RT_TagBase+94) +* maximum display height allowed +RTSC_MaxHeight equ (RT_TagBase+95) +* minimum display depth allowed +RTSC_MinDepth equ (RT_TagBase+96) +* maximum display depth allowed +RTSC_MaxDepth equ (RT_TagBase+97) +* call this hook for every display mode id +RTSC_FilterFunc equ RTFI_FilterFunc + +*** tags for rtChangeReqAttrA *** +* +* file requester - set directory +RTFI_Dir equ (RT_TagBase+50) +* file requester - set wildcard pattern +RTFI_MatchPat equ (RT_TagBase+51) +* file requester - add a file or directory to the buffer +RTFI_AddEntry equ (RT_TagBase+52) +* file requester - remove a file or directory from the buffer +RTFI_RemoveEntry equ (RT_TagBase+53) +* font requester - set font name of selected font +RTFO_FontName equ (RT_TagBase+63) +* font requester - set font size +RTFO_FontHeight equ (RT_TagBase+64) +* font requester - set font style +RTFO_FontStyle equ (RT_TagBase+65) +* font requester - set font flags +RTFO_FontFlags equ (RT_TagBase+66) +* (V38) screenmode requester - get display attributes from screen +RTSC_ModeFromScreen equ (RT_TagBase+80) +* (V38) screenmode requester - set display mode id (32-bit extended) +RTSC_DisplayID equ (RT_TagBase+81) +* (V38) screenmode requester - set display width +RTSC_DisplayWidth equ (RT_TagBase+82) +* (V38) screenmode requester - set display height +RTSC_DisplayHeight equ (RT_TagBase+83) +* (V38) screenmode requester - set display depth +RTSC_DisplayDepth equ (RT_TagBase+84) +* (V38) screenmode requester - set overscan type, 0 for regular size +RTSC_OverscanType equ (RT_TagBase+85) +* (V38) screenmode requester - set autoscroll +RTSC_AutoScroll equ (RT_TagBase+86) + +*** tags for rtPaletteRequestA *** +* +* initially selected color - default 1 +RTPA_Color equ (RT_TagBase+70) + +*** tags for rtReqHandlerA *** +* +* end requester by software control, set tagdata to REQ_CANCEL, REQ_OK or +* in case of rtEZRequest to the return value +RTRH_EndRequest equ (RT_TagBase+60) + +*** tags for rtAllocRequestA *** +* no tags defined yet + + +************* +* RT_ReqPos * +************* +REQPOS_POINTER equ 0 +REQPOS_CENTERWIN equ 1 +REQPOS_CENTERSCR equ 2 +REQPOS_TOPLEFTWIN equ 3 +REQPOS_TOPLEFTSCR equ 4 + +******************* +* RTRH_EndRequest * +******************* +REQ_CANCEL equ 0 +REQ_OK equ 1 + +**************************************** +* flags for RTFI_Flags and RTFO_Flags * +* or filereq->Flags and fontreq->Flags * +**************************************** + BITDEF FREQ,NOBUFFER,2 + +****************************************** +* flags for RTFI_Flags or filereq->Flags * +****************************************** + BITDEF FREQ,MULTISELECT,0 + BITDEF FREQ,SAVE,1 + BITDEF FREQ,NOFILES,3 + BITDEF FREQ,PATGAD,4 + BITDEF FREQ,SELECTDIRS,12 + +****************************************** +* flags for RTFO_Flags or fontreq->Flags * +****************************************** + BITDEF FREQ,FIXEDWIDTH,5 + BITDEF FREQ,COLORFONTS,6 + BITDEF FREQ,CHANGEPALETTE,7 + BITDEF FREQ,LEAVEPALETTE,8 + BITDEF FREQ,SCALE,9 + BITDEF FREQ,STYLE,10 + +****************************************************** +* (V38) flags for RTSC_Flags or screenmodereq->Flags * +****************************************************** + BITDEF SCREQ,SIZEGADS,13 + BITDEF SCREQ,DEPTHGAD,14 + BITDEF SCREQ,NONSTDMODES,15 + BITDEF SCREQ,GUIMODES,16 + BITDEF SCREQ,AUTOSCROLLGAD,18 + BITDEF SCREQ,OVERSCANGAD,19 + +****************************************** +* flags for RTEZ_Flags or reqinfo->Flags * +****************************************** + BITDEF EZREQ,NORETURNKEY,0 + BITDEF EZREQ,LAMIGAQUAL,1 + BITDEF EZREQ,CENTERTEXT,2 + +************************************************ +* (V38) flags for RTGL_Flags or reqinfo->Flags * +************************************************ + BITDEF GLREQ,CENTERTEXT,EZREQB_CENTERTEXT + BITDEF GLREQ,HIGHLIGHTTEXT,3 + +************************************************ +* (V38) flags for RTGS_Flags or reqinfo->Flags * +************************************************ + BITDEF GSREQ,CENTERTEXT,EZREQB_CENTERTEXT + BITDEF GSREQ,HIGHLIGHTTEXT,GLREQB_HIGHLIGHTTEXT + +****************************************** +* (V38) flags for RTFI_VolumeRequest tag * +****************************************** + BITDEF VREQ,NOASSIGNS,0 + BITDEF VREQ,NODISKS,1 + BITDEF VREQ,ALLDISKS,2 + +* +* Following things are obsolete in ReqTools V38. +* DON'T USE THESE IN NEW CODE! +* + IFND NO_REQTOOLS_OBSOLETE +rtfi_Hook equ rtfi_private1 +rtfo_Hook equ rtfo_private1 +REQHOOK_WILDFILE equ 0 +REQHOOK_WILDFONT equ 1 + BITDEF FREQ,DOWILDFUNC,11 + ENDC + + ENDC ; LIBRARIES_REQTOOLS_I diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/libraries/reqtools_lib.i b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/libraries/reqtools_lib.i new file mode 100644 index 0000000..b7a0409 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/libraries/reqtools_lib.i @@ -0,0 +1,52 @@ + IFND LIBRARIES_REQTOOLS_LIB_I +LIBRARIES_REQTOOLS_LIB_I SET 1 +** +** $Filename: libraries/reqtools_lib.i $ +** $Release: 2.2 $ +** +** (C) Copyright 1991-1994 Nico François +** All Rights Reserved +** + + IFND EXEC_TYPES_I + include "exec/types.i" + ENDC + IFND EXEC_NODES_I + include "exec/nodes.i" + ENDC + IFND EXEC_LISTS_I + include "exec/lists.i" + ENDC + IFND EXEC_LIBRARIES_I + include "exec/libraries.i" + ENDC + + LIBINIT + + LIBDEF _LVOrtAllocRequestA + LIBDEF _LVOrtFreeRequest + LIBDEF _LVOrtFreeReqBuffer + LIBDEF _LVOrtChangeReqAttrA + LIBDEF _LVOrtFileRequestA + LIBDEF _LVOrtFreeFileList + LIBDEF _LVOrtEZRequestA + LIBDEF _LVOrtGetStringA + LIBDEF _LVOrtGetLongA + LIBDEF _LVOrtInternalGetPasswordA ; private! + LIBDEF _LVOrtInternalEnterPasswordA ; private! + LIBDEF _LVOrtFontRequestA + LIBDEF _LVOrtPaletteRequestA + LIBDEF _LVOrtReqHandlerA + LIBDEF _LVOrtSetWaitPointer + LIBDEF _LVOrtGetVScreenSize + LIBDEF _LVOrtSetReqPosition + LIBDEF _LVOrtSpread + LIBDEF _LVOrtScreenToFrontSafely + LIBDEF _LVOrtScreenModeRequestA + LIBDEF _LVOrtCloseWindowSafely + LIBDEF _LVOrtLockWindow + LIBDEF _LVOrtUnlockWindow + LIBDEF _LVOrtLockPrefs + LIBDEF _LVOrtUnlockPrefs + + ENDC ; LIBRARIES_REQTOOLS_LIB_I diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/pragmas/reqtools.h b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/pragmas/reqtools.h new file mode 100644 index 0000000..cc19416 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/pragmas/reqtools.h @@ -0,0 +1,33 @@ + +/* + * reqtools.library © 1991-1994 Nico François + * + */ + +#pragma libcall ReqToolsBase rtAllocRequestA 1E 8002 +#pragma libcall ReqToolsBase rtFreeRequest 24 901 +#pragma libcall ReqToolsBase rtFreeReqBuffer 2A 901 +#pragma libcall ReqToolsBase rtChangeReqAttrA 30 8902 +#pragma libcall ReqToolsBase rtFileRequestA 36 8BA904 +#pragma libcall ReqToolsBase rtFreeFileList 3C 801 +#pragma libcall ReqToolsBase rtEZRequestA 42 8CBA905 +#pragma libcall ReqToolsBase rtGetStringA 48 8BA0905 +#pragma libcall ReqToolsBase rtGetLongA 4E 8BA904 +#pragma libcall ReqToolsBase rtFontRequestA 60 8B903 +#pragma libcall ReqToolsBase rtPaletteRequestA 66 8BA03 +#pragma libcall ReqToolsBase rtReqHandlerA 6C 80903 +#pragma libcall ReqToolsBase rtSetWaitPointer 72 801 +#pragma libcall ReqToolsBase rtGetVScreenSize 78 A9803 +#pragma libcall ReqToolsBase rtSetReqPosition 7E A98004 +#pragma libcall ReqToolsBase rtSpread 84 32109806 +#pragma libcall ReqToolsBase rtScreenToFrontSafely 8A 801 +#pragma libcall ReqToolsBase rtScreenModeRequestA 90 8B903 +#pragma libcall ReqToolsBase rtCloseWindowSafely 96 801 +#pragma libcall ReqToolsBase rtLockWindow 9C 801 +#pragma libcall ReqToolsBase rtUnlockWindow A2 9802 +/**/ +/* Private function only to be used by the ReqTools Preference editor.*/ +/* Only present in library versions _above_ 38.362 [1.3] and 38.810 [2.0]!*/ +/**/ +#pragma libcall ReqToolsBase rtLockPrefs A8 0 +#pragma libcall ReqToolsBase rtUnlockPrefs AE 0 diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/proto/reqtools.h b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/proto/reqtools.h new file mode 100644 index 0000000..8ea3c24 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/proto/reqtools.h @@ -0,0 +1,4 @@ +#include +extern struct ReqToolsBase *ReqToolsBase; +#include +#include diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/hooks.h b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/hooks.h new file mode 100644 index 0000000..d9f86b4 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/hooks.h @@ -0,0 +1,25 @@ +#ifndef UTILITY_HOOKS_H +#define UTILITY_HOOKS_H +/* +** $Filename: utility/tagitem.i $ +** $Release: 1.0 $ +** +** Clone of 2.0 include file 'utility/hooks.h' +*/ + +#ifndef EXEC_TYPES_H +#include "exec/types.h" +#endif /* EXEC_TYPES_H */ + +#ifndef EXEC_NODES_H +#include "exec/nodes.h" +#endif /* EXEC_NODES_H */ + +struct Hook { + struct MinNode h_MinNode; + ULONG (*h_Entry)(); + ULONG (*h_SubEntry)(); + APTR h_Data; + }; + +#endif /* UTILITY_HOOKS_H */ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/hooks.i b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/hooks.i new file mode 100644 index 0000000..a23aff5 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/hooks.i @@ -0,0 +1,24 @@ + IFND UTILITY_HOOKS_I +UTILITY_HOOKS_I SET 1 +** +** $Filename: utility/tagitem.i $ +** $Release: 1.0 $ +** +** Clone of 2.0 include file 'utility/hools.i' +** + + IFND EXEC_TYPES_I + INCLUDE "exec/types.i" + ENDC + + IFND EXEC_NODES_I + INCLUDE "exec/nodes.i" + ENDC + + STRUCTURE HOOK,MLN_SIZE + APTR h_Entry + APTR h_SubEntry + APTR h_Data + LABEL h_SIZEOF + + ENDC ; UTILITY_HOOKS_I diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/tagitem.h b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/tagitem.h new file mode 100644 index 0000000..e7b2db2 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/tagitem.h @@ -0,0 +1,30 @@ +#ifndef UTILITY_TAGITEM_H +#define UTILITY_TAGITEM_H +/* +** $Filename: utility/tagitem.i $ +** $Release: 1.0 $ +** +** Clone of 2.0 include file 'utility/tagitem.h' +*/ + +#ifndef EXEC_TYPES_H +#include +#endif /* EXEC_TYPES_H */ + +typedef ULONG Tag; + +struct TagItem { + Tag ti_Tag; + ULONG ti_Data; + }; + +#define TAG_DONE 0L +#define TAG_END 0L +#define TAG_IGNORE 1L +#define TAG_MORE 2L +#define TAG_SKIP 3L +#define TAG_USER 0x80000000 +#define TAGFILTER_AND 0 +#define TAGFILTER_NOT 1 + +#endif /* UTILITY_TAGITEM_H */ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/tagitem.i b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/tagitem.i new file mode 100644 index 0000000..6ed22a4 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtools/utility/tagitem.i @@ -0,0 +1,22 @@ + IFND UTILITY_TAGITEM_I +UTILITY_TAGITEM_I SET 1 +** +** $Filename: utility/tagitem.i $ +** $Release: 1.0 $ +** +** Clone of 2.0 include file 'utility/tagitem.i' +** + + STRUCTURE TagItem,0 + ULONG ti_Tag + ULONG ti_Data + LABEL ti_SIZEOF + +TAG_END equ 0 +TAG_DONE equ 0 +TAG_IGNORE equ 1 +TAG_MORE equ 2 +TAG_SKIP equ 3 +TAG_USER equ $80000000 + + ENDC ; UTILITY_TAGITEM_I diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtoolsnb.lib b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtoolsnb.lib new file mode 100644 index 0000000..70bd01b Binary files /dev/null and b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/reqtoolsnb.lib differ diff --git a/platforms/amiga/pistorm-dev/pistorm_dev_amiga/simple_interact.c b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/simple_interact.c new file mode 100644 index 0000000..2a07fe8 --- /dev/null +++ b/platforms/amiga/pistorm-dev/pistorm_dev_amiga/simple_interact.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT + +#include "../pistorm-dev-enums.h" +#include "pistorm_dev.h" + +//#define SHUTUP_VSCODE + +#ifdef SHUTUP_VSCODE +#define __stdargs +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#endif + +#include +#include +#include + +#define LOADLIB(a, b) if ((a = (struct a*)OpenLibrary(b,0L))==NULL) { \ + printf("Failed to load %s.\n", b); \ + return 1; \ + } + +void print_usage(char *exe); +int get_command(char *cmd); + +extern unsigned int pistorm_base_addr; + +unsigned short cmd_arg = 0; + +int __stdargs main (int argc, char *argv[]) { + if (argc < 2) { + print_usage(argv[0]); + return 1; + } + + int command = get_command(argv[1]); + if (command == -1) { + printf("Unknown command %s.\n", argv[1]); + return 1; + } + + pistorm_base_addr = pi_find_pistorm(); + + if (pistorm_base_addr == 0xFFFFFFFF) { + printf ("Unable to find PiStorm autoconf device.\n"); + return 1; + } + else { + printf ("PiStorm autoconf device found at $%.X\n", pistorm_base_addr); + } + + unsigned int tmpvalue = 0; + unsigned short tmpshort = 0; + + if (tmpvalue) {}; + + switch (command) { + case PI_CMD_RESET: + if (argc >= 3) + tmpshort = (unsigned short)atoi(argv[2]); + pi_reset_amiga(tmpshort); + break; + case PI_CMD_SWREV: + printf ("PiStorm ----------------------------\n"); + printf ("Hardware revision: %d.%d\n", (pi_get_hw_rev() >> 8), (pi_get_hw_rev() & 0xFF)); + printf ("Software revision: %d.%d\n", (pi_get_sw_rev() >> 8), (pi_get_sw_rev() & 0xFF)); + printf ("RTG: %s - %s\n", (pi_get_rtg_status() & 0x01) ? "Enabled" : "Disabled", (pi_get_rtg_status() & 0x02) ? "In use" : "Not in use"); + printf ("NET: %s\n", pi_get_net_status() ? "Enabled" : "Disabled"); + printf ("PiSCSI: %s\n", pi_get_piscsi_status() ? "Enabled" : "Disabled"); + break; + case PI_CMD_SWITCHCONFIG: + if (cmd_arg == PICFG_LOAD) { + if (argc < 3) { + printf ("User asked to load config, but no config filename specified.\n"); + } + } + pi_handle_config(cmd_arg, argv[2]); + break; + case PI_CMD_TRANSFERFILE: + if (argc < 4) { + printf ("Please specify a source and destination filename in addition to the command.\n"); + printf ("Example: %s --transfer platforms/platform.h snakes.h\n", argv[0]); + } + if (pi_get_filesize(argv[2], &tmpvalue) == PI_RES_FILENOTFOUND) { + printf ("File %s not found on the Pi side.\n", argv[2]); + } else { + unsigned int filesize = tmpvalue; + unsigned char *dest = malloc(filesize); + + if (dest == NULL) { + printf ("Failed to allocate memory buffer for file. Aborting file transfer.\n"); + } else { + printf ("Found a %u byte file on the Pi side. Eating it.\n", filesize); + if (pi_transfer_file(argv[2], dest) != PI_RES_OK) { + printf ("Something went horribly wrong during the file transfer.\n"); + } else { + FILE *out = fopen(argv[3], "wb+"); + if (out == NULL) { + printf ("Failed to open output file %s for writing.\n", argv[3]); + } else { + fwrite(dest, filesize, 1, out); + fclose(out); + printf ("%u bytes transferred to file %s.\n", filesize, argv[3]); + } + } + free(dest); + } + } + break; + default: + printf ("Unhandled command %s.\n", argv[1]); + return 1; + break; + } + + return 0; +} + +int get_command(char *cmd) { + if (strcmp(cmd, "--restart") == 0 || strcmp(cmd, "--reboot") == 0 || strcmp(cmd, "--reset") == 0) { + return PI_CMD_RESET; + } + if (strcmp(cmd, "--check") == 0 || strcmp(cmd, "--find") == 0 || strcmp(cmd, "--info") == 0) { + return PI_CMD_SWREV; + } + if (strcmp(cmd, "--config") == 0 || strcmp(cmd, "--config-file") == 0 || strcmp(cmd, "--cfg") == 0) { + cmd_arg = PICFG_LOAD; + return PI_CMD_SWITCHCONFIG; + } + if (strcmp(cmd, "--config-reload") == 0 || strcmp(cmd, "--reload-config") == 0 || strcmp(cmd, "--reloadcfg") == 0) { + cmd_arg = PICFG_RELOAD; + return PI_CMD_SWITCHCONFIG; + } + if (strcmp(cmd, "--config-default") == 0 || strcmp(cmd, "--default-config") == 0 || strcmp(cmd, "--defcfg") == 0) { + cmd_arg = PICFG_DEFAULT; + return PI_CMD_SWITCHCONFIG; + } + if (strcmp(cmd, "--transfer-file") == 0 || strcmp(cmd, "--transfer") == 0 || strcmp(cmd, "--getfile") == 0) { + return PI_CMD_TRANSFERFILE; + } + + return -1; +} + +void print_usage(char *exe) { + printf ("Usage: %s --[command] (arguments)\n", exe); + printf ("Example: %s --restart, --reboot or --reset\n", exe); + printf (" Restarts the Amiga.\n"); + printf (" %s --check, --find or --info\n", exe); + printf (" Finds the PiStorm device and prints some data.\n"); + + return; +} diff --git a/platforms/amiga/pistorm.hdf b/platforms/amiga/pistorm.hdf new file mode 100644 index 0000000..3b0f356 Binary files /dev/null and b/platforms/amiga/pistorm.hdf differ diff --git a/platforms/amiga/rtg/PiGFX Install.info b/platforms/amiga/rtg/PiGFX Install.info new file mode 100644 index 0000000..34ad980 Binary files /dev/null and b/platforms/amiga/rtg/PiGFX Install.info differ diff --git a/platforms/amiga/rtg/PiGFX Install/Installer b/platforms/amiga/rtg/PiGFX Install/Installer new file mode 100644 index 0000000..0f6f32a Binary files /dev/null and b/platforms/amiga/rtg/PiGFX Install/Installer differ diff --git a/platforms/amiga/rtg/PiGFX Install/Patch/GPatch b/platforms/amiga/rtg/PiGFX Install/Patch/GPatch new file mode 100644 index 0000000..88b3f33 Binary files /dev/null and b/platforms/amiga/rtg/PiGFX Install/Patch/GPatch differ diff --git a/platforms/amiga/rtg/PiGFX Install/Patch/P96Settings.patch b/platforms/amiga/rtg/PiGFX Install/Patch/P96Settings.patch new file mode 100644 index 0000000..97fe910 Binary files /dev/null and b/platforms/amiga/rtg/PiGFX Install/Patch/P96Settings.patch differ diff --git a/platforms/amiga/rtg/PiGFX Install/Patch/PiGFX.patch b/platforms/amiga/rtg/PiGFX Install/Patch/PiGFX.patch new file mode 100644 index 0000000..3ee0593 Binary files /dev/null and b/platforms/amiga/rtg/PiGFX Install/Patch/PiGFX.patch differ diff --git a/platforms/amiga/rtg/PiGFX Install/PiGFX Installer b/platforms/amiga/rtg/PiGFX Install/PiGFX Installer new file mode 100644 index 0000000..d569e8b --- /dev/null +++ b/platforms/amiga/rtg/PiGFX Install/PiGFX Installer @@ -0,0 +1,278 @@ +; PiGFX + +; Generated by InstallerGen 1.5! + +(complete 0) +(set @app-name "PiGFX") +(set @default-dest "Sys:") +; +(welcome "This installer will configure your Picasso96 installation for use with PiGFX RTG.\n\nAny old PiGFX card or monitor files that are detected will be removed.") + +(complete 3) +; Reminder to install P96 +(message "Please ensure you have already installed Picasso96. You can choose any GFX board (or select none at all) during the install process as this will not be used. \n\nThe install process will ask for the location of the original Picasso96 files that you used to install it. Please make sure to have them available. " +) + +(complete 6) +; Check if P96 is installed +(set #P96check + (exists "Sys:Prefs/Picasso96Mode" (noreq)) +) + +(complete 9) +; If Picasso96Mode exists +(if (= #P96check 0) +( + +(complete 12) +; Advise to install P96 and exit +(exit "Picasso96 does not appear to be installed.\n\nOnce Picasso96 has been installed, please re-run the PiGFX installer.") + +) +) ; End If +(complete 15) +; Check if old PiGFX monitor file exists +(set #OldPiGFX + (exists "Devs:Monitors/PiGFX" (noreq)) +) + +(complete 18) +; If old PiGFX monitor file exists +(if (= #OldPiGFX 1) +( + +(complete 21) +; Warn that old PiGFX file will be removed +(message "An old PiGFX monitor file has been found in Devs:Monitors/\n\nThis will now be removed. " +) + +(complete 24) +; Delete old PiGFX file +(delete "Devs:Monitors/PiGFX" + (prompt "Deleting old PiGFX monitor file.") + (help "A previous version of the PiGFX monitor file has been detected in Devs:Monitors/ and will be removed.") + (infos) + (optional "askuser") +) + +) +) ; End If +(complete 26) +; Check if old pigfx020 card file exists +(set #Old020card + (exists "Libs:Picasso96/pigfx020.card" (noreq)) +) + +(complete 29) +; If old pigfx020 card file exists +(if (= #Old020card 1) +( + +(complete 32) +; Warn that old pigfx020 card file will be removed +(message "An old pigfx020.card file has been found in Libs:Picasso96/\n\nThis will now be removed. " +) + +(complete 35) +; Delete old pigfx020 card file +(delete "Libs:Picasso96/pigfx020.card" + (prompt "Deleting old PiGFX monitor file.") + (help "A previous version of the pigfx020.card file has been detected in Libs:Picasso96/ and will be removed.") + (infos) + (optional "askuser") +) + +) +) ; End If +(complete 38) +; Check if old pigfx030 card file exists +(set #Old030card + (exists "Libs:Picasso96/pigfx030.card" (noreq)) +) + +(complete 41) +; If old pigfx030 card file exists +(if (= #Old030card 1) +( + +(complete 44) +; Warn that old pigfx030 card file will be removed +(message "An old pigfx030.card file has been found in Libs:Picasso96/\n\nThis will now be removed. " +) + +(complete 47) +; Delete old pigfx030 card file +(delete "Libs:Picasso96/pigfx030.card" + (prompt "Deleting old PiGFX monitor file.") + (help "A previous version of the pigfx030.card file has been detected in Libs:Picasso96/ and will be removed.") + (infos) + (optional "askuser") +) + +) +) ; End If +(complete 50) +; Check if P96Settings exists +(set #P96settingsold + (exists "Devs:Picasso96Settings" (noreq)) +) + +(complete 53) +; If P96Settings exists +(if (= #P96settingsold 1) +( + +(complete 56) +; Backup or delete old settings +(set #backupoptions + (askchoice + (prompt "Do you wish to backup your old Picasso96Settings file to Picasso96Settings.old.\n\nIf you select \"No\" then your settings file will be deleted.") + (help "There is already an existing Picasso96Settings file located in Devs: \n\nPlease choose if to backup this file to Picasso96Settings.old or if it can be deleted.") + (choices "Yes" "No") + (default 0) + ) +) +(if (= #backupoptions 0) (set #P96backup 1) (set #P96backup 0)) +(if (= #backupoptions 1) (set #P96delete 1) (set #P96delete 0)) + +) +) ; End If +(complete 59) +; Backup old settings +(if (= #P96backup 1) +( + +(complete 62) +; Check for old settings backup +(set #P96settingsbackupold + (exists "Devs:Picasso96Settings.old" (noreq)) +) + +) +) ; End If +(complete 65) +; If old P96Settings backup already exists +(if (= #P96settingsbackupold 1) +( + +(complete 68) +; Delete old P96Settings backup +(delete (tackon @default-dest "Devs/Picasso96Settings.old") + (prompt "Delting old Picasso96Settings.old file. ") + (help "The old back up of the Picasso96Settings file has been detected and will now be removed.") + (infos) + (optional "askuser") +) + +) +) ; End If +(complete 71) +; Create P96settings backup +(rename "Devs:Picasso96Settings" "Devs:Picasso96Settings.old" + (prompt "Creating backup of previous Picasso96Settings.") + (help ) +) + +(complete 74) +; If ok to delete old P96Settings +(if (= #P96settingsdelete 1) +( + +(complete 76) +; Delete old P96Settings file +(delete "Devs:Picasso96Settings" + (prompt "Deleting old Picasso96Settings file.") + (help "An old Picasso96Settings file has been detected in Devs: and will now be deleted.") + (infos) + (optional "askuser") +) + +) +) ; End If +(complete 79) +; Locate original P96 install files +(set #P96files + (askdir + (prompt "Please select the folder containing the original Picasso96 installation files (The folder is typically called Picasso96Install). These are required to create the PiGFX settings and monitor files. ") + (help "Please locate the original Picasso96 installtion files. This is the folder from which you originally installed Picasso96 and is usually called Picasso96Install.\n\nThis installation has to patch some of the original files to create the new PiGFX monitor and settings files. \n") + (default @default-dest) + ) +) + +(complete 82) +; Copy PiGFX card files +(copyfiles + (prompt "Copying PiGFX card files to Libs: ") + (help @copyfiles-help) + (source "") + (dest "Libs:Picasso96") + (choices "Files/pigfx020.card" "Files/pigfx030.card") +) + +(complete 85) +; Warn about temporary assign to install files +(message "A temporary assign called P96Temp: will be created to locate the original Picasso96 files. This will be removed once you reboot. " +) + +(complete 88) +; Make temporary assign to P96 install files +(makeassign "P96Temp" #P96files) + +; Check original p96settings exists +(set #OriginalP96settings + (exists "P96Temp:devs/Picasso96Settings.15" (noreq)) +) + +; If original P96settings does not exist +(if (= #OriginalP96settings 0) +( + +; Advise original settings cannot be found +(exit "The original Picasso96Settings file that is required to create the PiGFX settings file cannot be located.\n\nPlease ensure you set the correct path to the original installation files, and are using a compatible version of Picasso96 from Aminet or Individual Computers. Please see Readme.txt for more information.") + +) +) ; End If +; Check original monitor file exists +(set #OriginalP96monitor + (exists "P96Temp:devs/monitors/Picasso96" (noreq)) +) + +; If original monitor file does not exist +(if (= #OriginalP96monitor 0) +( + +; Advise original monitor cannot be found +(exit "The original Picasso96 monitor file that is required to create the PiGFX monitor file cannot be located.\n\nPlease ensure you set the correct path to the original installation files, and are using a compatible version of Picasso96 from Aminet or Individual Computers. Please see Readme.txt for more information.") + +) +) ; End If +(complete 91) +; Patching P96 monitor file +(run "Patch/gpatch" "P96Temp:devs/monitors/Picasso96" "Patch/PiGFX.patch" "Devs:Monitors/PiGFX" + (prompt "Patching original Picasso monitor file to create PiGFX monitor file in Devs:Monitors/") + (help "A PiGFX monitor file will be created by patching one of the original monitor files.") + (confirm "average") +) + +(complete 94) +; Patching P96Settings file +(run "Patch/gpatch" "P96Temp:devs/Picasso96Settings.15" "Patch/P96Settings.patch" "Devs:Picasso96Settings" + (prompt "Patching original Picasso96Settings file and creating PiGFX Devs:Picasso96Settings file. ") + (help "A new Picasso96Settings file for PiGFX will be created by patching on of the original settings files. ") + (confirm "average") +) + +(complete 97) +; Copy PiGFX icon +(copyfiles + (prompt "Copying the PiGFX icon and tooltypes to Devs:Monitors/") + (help "The will copy the PiGFX icon that contains the required tooltypes to Devs:Monitors/") + (source "") + (dest "Devs:Monitors") + (choices "Files/PiGFX.info") +) + +; Reminder to enable RTG +(message "Remember to enable RTG in the PiStorm default.cfg file." +) + diff --git a/platforms/amiga/rtg/PiGFX Install/PiGFX Installer.info b/platforms/amiga/rtg/PiGFX Install/PiGFX Installer.info new file mode 100644 index 0000000..f2b379e Binary files /dev/null and b/platforms/amiga/rtg/PiGFX Install/PiGFX Installer.info differ diff --git a/platforms/amiga/rtg/PiGFX Install/Readme.txt b/platforms/amiga/rtg/PiGFX Install/Readme.txt new file mode 100644 index 0000000..981be19 --- /dev/null +++ b/platforms/amiga/rtg/PiGFX Install/Readme.txt @@ -0,0 +1,62 @@ +PiGFX RTG Installer +=================== + +Unfortunately, there are some Picasso96 settings files required to use +PiGFX RTG on PiStorm that are not able to be freely distributed. + +This installer will patch files from the original install folder to +create the required monitor file and settings files. This will save +you having to create these and setting up all the resolutions manually. + +The card files currently in the installer are from the WIP-crap branch +as of 3rd June 2021. + +Requirements +============ + +This installer requires you to already have installed Picasso96 on +your PiStorm machine. + +During the Picasso96 installation you can choose any graphics board +(or select none at all) as this will be ignored and new files for PiGFX +will be created. + +The installer will also require access to the original installation +files that are located in the folder from where you installed Picasso96. + +At the time of writing, the below versions of Picasso96 are supported. +Please ensure that you are using one of these exact versions, and do not +modify the folder in any way. The installer requires the files to be in +their original locations. + +Picasso96 v2.0 - This can be downloaded freely from Aminet here - + https://aminet.net/package/driver/video/Picasso96 + +Picasso96 v3.02 - This can be purchased from Individual Computers for + a small fee, and at the time of writing the latest + version is v3.02. + +Any future versions may require a new patch file to be created. If/when +IComp do release any future updates, the patch files will be updated +accordingly if required. + +Installation +============ + +Simply run the installer and it will walk you through the process. + +Any old versions of the PiGFX files will be removed during the install +and new ones created by patching some of the original install files. + +During the install, you will be asked to locate the original +installation folder from where you installed Picasso96, this folder is +typically called Picasso96Install. + +Issues +====== + +If you have any issues with the installation, please contact us on +Discord for assistance. + + + diff --git a/platforms/amiga/rtg/PiGFX Install/Readme.txt.info b/platforms/amiga/rtg/PiGFX Install/Readme.txt.info new file mode 100644 index 0000000..01bccd5 Binary files /dev/null and b/platforms/amiga/rtg/PiGFX Install/Readme.txt.info differ diff --git a/platforms/amiga/rtg/argbswizzle.shader b/platforms/amiga/rtg/argbswizzle.shader new file mode 100644 index 0000000..3ea0c79 --- /dev/null +++ b/platforms/amiga/rtg/argbswizzle.shader @@ -0,0 +1,18 @@ +#version 100 + +precision mediump float; + +varying vec2 fragTexCoord; +varying vec4 fragColor; + +uniform sampler2D texture0; +uniform sampler2D texture1; + +uniform vec4 colDiffuse; + +void main() +{ + vec4 texelColor = texture2D(texture0, fragTexCoord); + + gl_FragColor = vec4(texelColor.b, texelColor.g, texelColor.r, 1.0); +} diff --git a/platforms/amiga/rtg/clut.shader b/platforms/amiga/rtg/clut.shader new file mode 100644 index 0000000..3472948 --- /dev/null +++ b/platforms/amiga/rtg/clut.shader @@ -0,0 +1,30 @@ +#version 100 + +precision mediump float; + +const int colors = 8; + +varying vec2 fragTexCoord; +varying vec4 fragColor; + +uniform sampler2D texture0; +uniform sampler2D texture1; + +uniform vec4 colDiffuse; + +uniform ivec3 palette[colors]; + +void main() +{ + vec4 texelColor = texture2D(texture0, fragTexCoord); + + vec4 color = vec4(1.0, 1.0, 1.0, 1.0); + vec2 bukCoord = vec2(texelColor.r, 0.5); + + if (texelColor.r == 1.0) { + bukCoord = vec2(0.9999, 0.5); + } + vec4 colorx = texture2D(texture1, bukCoord); + + gl_FragColor = vec4(colorx.r, colorx.g, colorx.b, colorx.a); +} diff --git a/platforms/amiga/rtg/irtg_structs.h b/platforms/amiga/rtg/irtg_structs.h new file mode 100644 index 0000000..8ceb213 --- /dev/null +++ b/platforms/amiga/rtg/irtg_structs.h @@ -0,0 +1,327 @@ +struct P96Line { + int16_t X, Y; + uint16_t Length; + int16_t dX, dY; + int16_t sDelta, lDelta, twoSDminusLD; + uint16_t LinePtrn; + uint16_t PatternShift; + uint32_t FgPen, BgPen; + uint16_t Horizontal; + uint8_t DrawMode; + int8_t pad; + uint16_t Xorigin, Yorigin; +}; + +#pragma pack(2) +struct P96Template { + uint32_t _p_Memory; + uint16_t BytesPerRow; + uint8_t XOffset; + uint8_t DrawMode; + uint32_t FgPen; + uint32_t BgPen; +}; + +#pragma pack(2) +struct P96Pattern { + uint32_t _p_Memory; + uint16_t XOffset, YOffset; + uint32_t FgPen, BgPen; + uint8_t Size; // Width: 16, Height: (1< +#include "platforms/amiga/rtg/irtg_structs.h" #include "rtg.h" extern uint32_t rtg_address[8]; extern uint32_t rtg_address_adj[8]; extern uint8_t *rtg_mem; // FIXME -extern uint16_t rtg_display_format; extern uint16_t rtg_user[8]; extern uint16_t rtg_x[8], rtg_y[8]; +extern uint16_t rtg_format; + +extern uint32_t framebuffer_addr; +extern uint32_t framebuffer_addr_adj; extern uint8_t realtime_graphics_debug; @@ -56,7 +61,7 @@ void rtg_fillrect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t color for (int ys = 0; ys < h; ys++) { for (int xs = 0; xs < w; xs++) { - SET_RTG_PIXEL_MASK(&dptr[xs], (color & 0xFF), format); + SET_RTG_PIXEL_MASK(&dptr[xs << format], (color & 0xFF), format); } dptr += pitch; } @@ -224,8 +229,6 @@ void rtg_blitrect_nomask_complete(uint16_t sx, uint16_t sy, uint16_t dx, uint16_ extern struct emulator_config *cfg; void rtg_blittemplate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t src_addr, uint32_t fgcol, uint32_t bgcol, uint16_t pitch, uint16_t t_pitch, uint16_t format, uint16_t offset_x, uint8_t mask, uint8_t draw_mode) { - if (mask) {} - uint8_t *dptr = &rtg_mem[rtg_address_adj[1] + (x << format) + (y * pitch)]; uint8_t *sptr = NULL; uint8_t cur_bit = 0, base_bit = 0, cur_byte = 0; @@ -239,7 +242,7 @@ void rtg_blittemplate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t s if (realtime_graphics_debug) { printf("DEBUG: BlitTemplate - %d, %d (%dx%d)\n", x, y, w, h); - printf("Src: %.8X (%.8X)\n", src_addr, rtg_address_adj[0]); + printf("Src: %.8X\n", src_addr); printf("Dest: %.8X (%.8X)\n", rtg_address[1], rtg_address_adj[1]); printf("pitch: %d t_pitch: %d format: %d\n", pitch, t_pitch, format); printf("offset_x: %d mask: %.2X draw_mode: %d\n", offset_x, mask, draw_mode); @@ -256,54 +259,22 @@ void rtg_blittemplate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t s htobe32(bgcol), }; - if (src_addr >= (PIGFX_RTG_BASE + PIGFX_REG_SIZE)) { - sptr = &rtg_mem[src_addr - (PIGFX_RTG_BASE + PIGFX_REG_SIZE)]; + sptr = get_mapped_data_pointer_by_address(cfg, src_addr); + if (!sptr) { if (realtime_graphics_debug) { - printf("Origin: %.8X\n", rtg_address[2]); - printf("Grabbing data from RTG memory.\nData:\n"); - for (int i = 0; i < h; i++) { - for (int j = 0; j < t_pitch; j++) { - printf("%.2X", sptr[j + (i * t_pitch)]); - } - printf("\n"); - } -#ifndef FAKESTORM - printf("Data available at origin:\n"); - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - printf("%.2X", read8(rtg_address[2] + j + (i * t_pitch))); - } - printf("\n"); - } -#endif + printf("BlitTemplate data NOT available in mapped range, source address: $%.8X\n", src_addr); } - } - else { - int i = get_mapped_item_by_address(cfg, src_addr); - if (i != -1) { - sptr = &cfg->map_data[i][src_addr - cfg->map_offset[i]]; - if (realtime_graphics_debug) { - printf("Grabbing data from maping %d - offset %.8lX\nData:\n", i, src_addr - cfg->map_offset[i]); - for (int i = 0; i < h; i++) { - for (int j = 0; j < t_pitch; j++) { - printf("%.2X", sptr[j + (i * t_pitch)]); - } - printf("\n"); - } - } - } - else { - printf("BlitTemplate: Failed to find mapped range for address %.8X\n", src_addr); - return; + } else { + if (realtime_graphics_debug) { + printf("BlitTemplate data available in mapped range at $%.8X\n", src_addr); } } switch (draw_mode) { case DRAWMODE_JAM1: for (uint16_t ys = 0; ys < h; ys++) { - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; - for (int xs = 0; xs < w; xs++) { + TEMPLATE_LOOPX; if (w >= 8 && cur_bit == 0x80 && xs < w - 8) { if (mask == 0xFF || format != RTGFMT_8BIT) { SET_RTG_PIXELS(&dptr[xs << format], fg_color[format], format); @@ -329,16 +300,14 @@ void rtg_blittemplate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t s xs--; cur_bit = 0x80; } - TEMPLATE_LOOPX; } TEMPLATE_LOOPY; } return; case DRAWMODE_JAM2: for (uint16_t ys = 0; ys < h; ys++) { - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; - for (int xs = 0; xs < w; xs++) { + TEMPLATE_LOOPX; if (w >= 8 && cur_bit == 0x80 && xs < w - 8) { if (mask == 0xFF || format != RTGFMT_8BIT) { SET_RTG_PIXELS2_COND(&dptr[xs << format], fg_color[format], bg_color[format], format); @@ -363,16 +332,14 @@ void rtg_blittemplate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t s xs--; cur_bit = 0x80; } - TEMPLATE_LOOPX; } TEMPLATE_LOOPY; } return; case DRAWMODE_COMPLEMENT: for (uint16_t ys = 0; ys < h; ys++) { - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; - for (int xs = 0; xs < w; xs++) { + TEMPLATE_LOOPX; if (w >= 8 && cur_bit == 0x80 && xs < w - 8) { INVERT_RTG_PIXELS(&dptr[xs << format], format) xs += 7; @@ -388,7 +355,6 @@ void rtg_blittemplate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t s xs--; cur_bit = 0x80; } - TEMPLATE_LOOPX; } TEMPLATE_LOOPY; } @@ -396,7 +362,7 @@ void rtg_blittemplate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t s } } -void rtg_blitpattern(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t src_addr, uint32_t fgcol, uint32_t bgcol, uint16_t pitch, uint16_t format, uint16_t offset_x, uint16_t offset_y, uint8_t mask, uint8_t draw_mode, uint8_t loop_rows) { +void rtg_blitpattern(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t src_addr_, uint32_t fgcol, uint32_t bgcol, uint16_t pitch, uint16_t format, uint16_t offset_x, uint16_t offset_y, uint8_t mask, uint8_t draw_mode, uint8_t loop_rows) { if (mask) {} uint8_t *dptr = &rtg_mem[rtg_address_adj[1] + (x << format) + (y * pitch)]; @@ -404,6 +370,8 @@ void rtg_blitpattern(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t sr uint8_t cur_bit = 0, base_bit = 0, cur_byte = 0; uint8_t invert = (draw_mode & DRAWMODE_INVERSVID); uint16_t tmpl_x = 0; + uint32_t src_addr = src_addr_; + uint32_t src_addr_base = src_addr; draw_mode &= 0x03; @@ -422,28 +390,25 @@ void rtg_blitpattern(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t sr }; - if (src_addr >= (PIGFX_RTG_BASE + PIGFX_REG_SIZE)) - sptr = &rtg_mem[src_addr - (PIGFX_RTG_BASE + PIGFX_REG_SIZE)]; - else { - int i = get_mapped_item_by_address(cfg, src_addr); - if (i != -1) { - sptr = &cfg->map_data[i][src_addr - cfg->map_offset[i]]; + sptr = get_mapped_data_pointer_by_address(cfg, src_addr); + if (!sptr) { + if (realtime_graphics_debug) { + printf("BlitPattern data NOT available in mapped range, source address: $%.8X\n", src_addr); + src_addr += (offset_y % loop_rows) * 2; } - else { - printf("BlitPattern: Failed to find mapped range for address %.8X\n", src_addr); - return; + } else { + if (realtime_graphics_debug) { + printf("BlitPattern data available in mapped range at $%.8X\n", src_addr); } + sptr_base = sptr; + sptr += (offset_y % loop_rows) * 2; } - sptr_base = sptr; - sptr += (offset_y % loop_rows) * 2; - switch (draw_mode) { case DRAWMODE_JAM1: for (uint16_t ys = 0; ys < h; ys++) { - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; - for (int xs = 0; xs < w; xs++) { + PATTERN_LOOPX; if (w >= 8 && cur_bit == 0x80 && xs < w - 8) { if (mask == 0xFF || format != RTGFMT_8BIT) { SET_RTG_PIXELS(&dptr[xs << format], fg_color[format], format); @@ -469,16 +434,14 @@ void rtg_blitpattern(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t sr xs--; cur_bit = 0x80; } - PATTERN_LOOPX; } PATTERN_LOOPY; } return; case DRAWMODE_JAM2: for (uint16_t ys = 0; ys < h; ys++) { - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; - for (int xs = 0; xs < w; xs++) { + PATTERN_LOOPX; if (w >= 8 && cur_bit == 0x80 && xs < w - 8) { if (mask == 0xFF || format != RTGFMT_8BIT) { SET_RTG_PIXELS2_COND(&dptr[xs << format], fg_color[format], bg_color[format], format); @@ -503,16 +466,14 @@ void rtg_blitpattern(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t sr xs--; cur_bit = 0x80; } - PATTERN_LOOPX; } PATTERN_LOOPY; } return; case DRAWMODE_COMPLEMENT: for (uint16_t ys = 0; ys < h; ys++) { - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; - for (int xs = 0; xs < w; xs++) { + PATTERN_LOOPX; if (w >= 8 && cur_bit == 0x80 && xs < w - 8) { INVERT_RTG_PIXELS(&dptr[xs << format], format) xs += 7; @@ -528,7 +489,6 @@ void rtg_blitpattern(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t sr xs--; cur_bit = 0x80; } - PATTERN_LOOPX; } PATTERN_LOOPY; } @@ -689,6 +649,92 @@ void rtg_drawline (int16_t x1_, int16_t y1_, int16_t x2_, int16_t y2_, uint16_t } } +// This is slow and somewhat useless, needs a rewrite to ps_read_16 copy the bit plane data +// similarly to what the code in the RTG driver does. Disabled for now. +void rtg_p2c_ex(int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t h, uint8_t minterm, struct BitMap *bm, uint8_t mask, uint16_t dst_pitch, uint16_t src_pitch) { + uint16_t pitch = dst_pitch; + uint8_t *dptr = &rtg_mem[rtg_address_adj[0] + (dy * pitch)]; + uint8_t draw_mode = minterm; + + uint8_t cur_bit, base_bit, base_byte; + uint16_t cur_byte = 0, u8_fg = 0, u8_tmp = 0; + + cur_bit = base_bit = (0x80 >> (sx % 8)); + cur_byte = base_byte = ((sx / 8) % src_pitch); + + uint8_t *plane_ptr[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + uint32_t plane_addr[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + for (int i = 0; i < bm->Depth; i++) { + uint32_t plane_address = be32toh(bm->_p_Planes[i]); + if (plane_address != 0 && plane_address != 0xFFFFFFFF) { + plane_ptr[i] = get_mapped_data_pointer_by_address(cfg, be32toh(bm->_p_Planes[i])); + if (!plane_ptr[i]) { + plane_addr[i] = be32toh(bm->_p_Planes[i]); + if (plane_addr[i] != 0) plane_addr[i] += (sy * src_pitch); + } else { + plane_ptr[i] += (sy * src_pitch); + } + } else { + plane_addr[i] = plane_address; + } + } + + for (int16_t line_y = 0; line_y < h; line_y++) { + for (int16_t x = dx; x < dx + w; x++) { + u8_fg = 0; + if (minterm & 0x01) { + for (int i = 0; i < bm->Depth; i++) { + if (plane_ptr[i]) { + if (~plane_ptr[i][cur_byte] & cur_bit) u8_fg |= (1 << i); + } else { + if (plane_addr[i] == 0xFFFFFFFF) u8_fg |= (1 << i); + else if (plane_addr[i] != 0) { + u8_tmp = (uint8_t)ps_read_8(plane_addr[i] + cur_byte); + if (~u8_tmp & cur_bit) u8_fg |= (1 << i); + } + } + } + } else { + for (int i = 0; i < bm->Depth; i++) { + if (plane_ptr[i]) { + if (plane_ptr[i][cur_byte] & cur_bit) u8_fg |= (1 << i); + } else { + if (plane_addr[i] == 0xFFFFFFFF) u8_fg |= (1 << i); + else if (plane_addr[i] != 0) { + u8_tmp = (uint8_t)ps_read_8(plane_addr[i] + cur_byte); + if (u8_tmp & cur_bit) u8_fg |= (1 << i); + } + } + } + } + + if (mask == 0xFF && (draw_mode == MINTERM_SRC || draw_mode == MINTERM_NOTSRC)) { + dptr[x] = u8_fg; + goto skip; + } + + HANDLE_MINTERM_PIXEL(u8_fg, dptr[x], RTGFMT_8BIT); + + skip:; + if ((cur_bit >>= 1) == 0) { + cur_bit = 0x80; + cur_byte++; + cur_byte %= src_pitch; + } + } + dptr += pitch; + for (int i = 0; i < bm->Depth; i++) { + if (plane_ptr[i] && (uint32_t)plane_ptr[i] != 0xFFFFFFFF) + plane_ptr[i] += src_pitch; + if (plane_addr[i] && plane_addr[i] != 0xFFFFFFFF) + plane_addr[i] += src_pitch; + } + cur_bit = base_bit; + cur_byte = base_byte; + } +} + void rtg_p2c (int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t h, uint8_t draw_mode, uint8_t planes, uint8_t mask, uint8_t layer_mask, uint16_t src_line_pitch, uint8_t *bmp_data_src) { uint16_t pitch = rtg_x[3]; uint8_t *dptr = &rtg_mem[rtg_address_adj[0] + (dy * pitch)]; @@ -740,7 +786,7 @@ void rtg_p2c (int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t goto skip; } - HANDLE_MINTERM_PIXEL(u8_fg, dptr[x], rtg_display_format); + HANDLE_MINTERM_PIXEL(u8_fg, dptr[x], rtg_format); skip:; if ((cur_bit >>= 1) == 0) { @@ -748,7 +794,87 @@ void rtg_p2c (int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t cur_byte++; cur_byte %= src_line_pitch; } + } + dptr += pitch; + if ((line_y + sy + 1) % h) + bmp_data += src_line_pitch; + else + bmp_data = bmp_data_src; + cur_bit = base_bit; + cur_byte = base_byte; + } +} +void rtg_p2d (int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t h, uint8_t draw_mode, uint8_t planes, uint8_t mask, uint8_t layer_mask, uint16_t src_line_pitch, uint8_t *bmp_data_src) { + uint16_t pitch = rtg_x[3]; + uint8_t *dptr = &rtg_mem[rtg_address_adj[0] + (dy * pitch)]; + + uint8_t cur_bit, base_bit, base_byte; + uint16_t cur_byte = 0, u8_fg = 0; + //uint32_t color_mask = 0xFFFFFFFF; + + uint32_t plane_size = src_line_pitch * h; + uint8_t *bmp_data = bmp_data_src; + + cur_bit = base_bit = (0x80 >> (sx % 8)); + cur_byte = base_byte = ((sx / 8) % src_line_pitch); + + if (realtime_graphics_debug) { + printf("P2D: %d,%d - %d,%d (%dx%d) %d, %.2X\n", sx, sy, dx, dy, w, h, planes, layer_mask); + printf("Mask: %.2X Minterm: %.2X\n", mask, draw_mode); + printf("Pitch: %d Src Pitch: %d (!!!: %.4X)\n", pitch, src_line_pitch, rtg_user[0]); + printf("Curbyte: %d Curbit: %d\n", cur_byte, cur_bit); + printf("Plane size: %d Total size: %d (%X)\n", plane_size, plane_size * planes, plane_size * planes); + printf("Source: %.8X - %.8X\n", rtg_address[1], rtg_address_adj[1]); + printf("Target: %.8X - %.8X\n", rtg_address[0], rtg_address_adj[0]); + fflush(stdout); + + printf("Grabbing data from RTG memory.\nData:\n"); + for (int i = 0; i < h; i++) { + for (int k = 0; k < planes; k++) { + for (int j = 0; j < src_line_pitch; j++) { + printf("%.2X", bmp_data_src[j + (i * src_line_pitch) + (plane_size * k)]); + } + printf(" "); + } + printf("\n"); + } + } + + uint32_t *clut = (uint32_t *)bmp_data_src; + bmp_data += (256 * 4); + bmp_data_src += (256 * 4); + + for (int16_t line_y = 0; line_y < h; line_y++) { + for (int16_t x = dx; x < dx + w; x++) { + u8_fg = 0; + if (draw_mode & 0x01) { + DECODE_INVERTED_PLANAR_PIXEL(u8_fg) + } + else { + DECODE_PLANAR_PIXEL(u8_fg) + } + + uint32_t fg_color = clut[u8_fg]; + + if (mask == 0xFF && (draw_mode == MINTERM_SRC || draw_mode == MINTERM_NOTSRC)) { + switch (rtg_format) { + case RTGFMT_RBG565: + ((uint16_t *)dptr)[x] = (fg_color >> 16); + break; + case RTGFMT_RGB32: + ((uint32_t *)dptr)[x] = fg_color; + break; + } + goto skip; + } + + skip:; + if ((cur_bit >>= 1) == 0) { + cur_bit = 0x80; + cur_byte++; + cur_byte %= src_line_pitch; + } } dptr += pitch; if ((line_y + sy + 1) % h) diff --git a/platforms/amiga/rtg/rtg-output-raylib.c b/platforms/amiga/rtg/rtg-output-raylib.c new file mode 100644 index 0000000..e29869b --- /dev/null +++ b/platforms/amiga/rtg/rtg-output-raylib.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: MIT + +#include "config_file/config_file.h" +#include "emulator.h" +#include "rtg.h" + +#include "raylib/raylib.h" + +#include +#include +#include +#include +#include +#include + +#define RTG_INIT_ERR(a) { printf(a); *data->running = 0; } + +//#define DEBUG_RAYLIB_RTG + +#ifdef DEBUG_RAYLIB_RTG +#define DEBUG printf +#else +#define DEBUG(...) +#endif + +uint8_t busy = 0, rtg_on = 0, rtg_initialized = 0, emulator_exiting = 0, rtg_output_in_vblank = 0; +extern uint8_t *rtg_mem, display_enabled; +extern uint32_t framebuffer_addr; +extern uint32_t framebuffer_addr_adj; + +extern uint16_t rtg_display_width, rtg_display_height; +extern uint16_t rtg_display_format; +extern uint16_t rtg_pitch, rtg_total_rows; +extern uint16_t rtg_offset_x, rtg_offset_y; + +uint32_t cur_rtg_frame = 0; + +static pthread_t thread_id; +static uint8_t mouse_cursor_enabled = 0, cursor_image_updated = 0, updating_screen = 0, debug_palette = 0, show_fps = 0; +static uint8_t mouse_cursor_w = 16, mouse_cursor_h = 16; +static int16_t mouse_cursor_x = 0, mouse_cursor_y = 0; + +struct rtg_shared_data { + uint16_t *width, *height; + uint16_t *format, *pitch; + uint16_t *offset_x, *offset_y; + uint8_t *memory; + uint32_t *addr; + uint8_t *running; +}; + +struct rtg_shared_data rtg_share_data; +static uint32_t palette[256]; +static uint32_t cursor_palette[256]; + +uint32_t cursor_data[256 * 256]; + +void rtg_update_screen() {} + +uint32_t rtg_to_raylib[RTGFMT_NUM] = { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE, + PIXELFORMAT_UNCOMPRESSED_R5G6B5, + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, +}; + +uint32_t rtg_pixel_size[RTGFMT_NUM] = { + 1, + 2, + 4, + 2, +}; + +void *rtgThread(void *args) { + + printf("RTG thread running\n"); + fflush(stdout); + + int reinit = 0; + rtg_on = 1; + + uint32_t *indexed_buf = NULL; + + rtg_share_data.format = &rtg_display_format; + rtg_share_data.width = &rtg_display_width; + rtg_share_data.height = &rtg_display_height; + rtg_share_data.pitch = &rtg_pitch; + rtg_share_data.offset_x = &rtg_offset_x; + rtg_share_data.offset_y = &rtg_offset_y; + rtg_share_data.memory = rtg_mem; + rtg_share_data.running = &rtg_on; + rtg_share_data.addr = &framebuffer_addr_adj; + struct rtg_shared_data *data = &rtg_share_data; + + uint16_t width = rtg_display_width; + uint16_t height = rtg_display_height; + uint16_t format = rtg_display_format; + uint16_t pitch = rtg_pitch; + + Texture raylib_texture, raylib_cursor_texture; + Texture raylib_clut_texture; + Image raylib_fb, raylib_cursor, raylib_clut; + + InitWindow(GetScreenWidth(), GetScreenHeight(), "Pistorm RTG"); + HideCursor(); + SetTargetFPS(60); + + Color bef = { 0, 64, 128, 255 }; + float scale_x = 1.0f, scale_y = 1.0f; + + Shader clut_shader = LoadShader(NULL, "platforms/amiga/rtg/clut.shader"); + Shader swizzle_shader = LoadShader(NULL, "platforms/amiga/rtg/argbswizzle.shader"); + int clut_loc = GetShaderLocation(clut_shader, "texture1"); + + raylib_clut.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + raylib_clut.width = 256; + raylib_clut.height = 1; + raylib_clut.mipmaps = 1; + raylib_clut.data = palette; + + raylib_clut_texture = LoadTextureFromImage(raylib_clut); + + raylib_cursor.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + raylib_cursor.width = 256; + raylib_cursor.height = 256; + raylib_cursor.mipmaps = 1; + raylib_cursor.data = cursor_data; + raylib_cursor_texture = LoadTextureFromImage(raylib_cursor); + + Rectangle srcrect, dstscale; + Vector2 origin; + +reinit_raylib:; + if (reinit) { + printf("Reinitializing raylib...\n"); + width = rtg_display_width; + height = rtg_display_height; + format = rtg_display_format; + pitch = rtg_pitch; + if (indexed_buf) { + free(indexed_buf); + indexed_buf = NULL; + } + UnloadTexture(raylib_texture); + reinit = 0; + } + + printf("Creating %dx%d raylib window...\n", width, height); + + printf("Setting up raylib framebuffer image.\n"); + raylib_fb.format = rtg_to_raylib[format]; + + switch (format) { + case RTGFMT_RBG565: + raylib_fb.width = width; + indexed_buf = calloc(1, width * height * 2); + break; + default: + raylib_fb.width = pitch / rtg_pixel_size[format]; + break; + } + raylib_fb.height = height; + raylib_fb.mipmaps = 1; + raylib_fb.data = &data->memory[*data->addr]; + printf("Width: %d\nPitch: %d\nBPP: %d\n", raylib_fb.width, pitch, rtg_pixel_size[format]); + + raylib_texture = LoadTextureFromImage(raylib_fb); + + printf("Loaded framebuffer texture.\n"); + + srcrect.x = srcrect.y = 0; + srcrect.width = width; + srcrect.height = height; + dstscale.x = dstscale.y = 0; + dstscale.width = width; + dstscale.height = height; + + if (dstscale.height * 2 <= GetScreenHeight()) { + if (width == 320) { + if (GetScreenHeight() == 720) { + dstscale.width = 960; + dstscale.height = 720; + } else if (GetScreenHeight() == 1080) { + dstscale.width = 1440; + dstscale.height = 1080; + } else if (GetScreenHeight() == 1200) { + dstscale.width = 1600; + dstscale.height = 1200; + } + } else { + while (dstscale.height + height <= GetScreenHeight()) { + dstscale.height += height; + dstscale.width += width; + } + } + } else if (dstscale.width > GetScreenWidth() || dstscale.height > GetScreenHeight()) { + if (dstscale.height > GetScreenHeight()) { + DEBUG("[H > SH]\n"); + DEBUG("Adjusted width from %d to", (int)dstscale.width); + dstscale.width = dstscale.width * ((float)GetScreenHeight() / (float)height); + DEBUG("%d.\n", (int)dstscale.width); + DEBUG("Adjusted height from %d to", (int)dstscale.height); + dstscale.height = GetScreenHeight(); + DEBUG("%d.\n", (int)dstscale.height); + } + if (dstscale.width > GetScreenWidth()) { + // First scaling attempt failed, do not double adjust, re-adjust + dstscale.width = width; + dstscale.height = height; + DEBUG("[W > SW]\n"); + DEBUG("Adjusted height from %d to", (int)dstscale.height); + dstscale.height = dstscale.height * ((float)GetScreenWidth() / (float)width); + DEBUG("%d.\n", (int)dstscale.height); + DEBUG("Adjusted width from %d to", (int)dstscale.width); + dstscale.width = GetScreenWidth(); + DEBUG("%d.\n", (int)dstscale.width); + } + } + + scale_x = dstscale.width / (float)width; + scale_y = dstscale.height / (float)height; + + origin.x = (dstscale.width - GetScreenWidth()) * 0.5; + origin.y = (dstscale.height - GetScreenHeight()) * 0.5; + + while (1) { + if (rtg_on) { + BeginDrawing(); + rtg_output_in_vblank = 0; + updating_screen = 1; + + switch (format) { + case RTGFMT_8BIT: + UpdateTexture(raylib_clut_texture, palette); + BeginShaderMode(clut_shader); + SetShaderValueTexture(clut_shader, clut_loc, raylib_clut_texture); + break; + case RTGFMT_RGB32: + BeginShaderMode(swizzle_shader); + break; + } + + DrawTexturePro(raylib_texture, srcrect, dstscale, origin, 0.0f, RAYWHITE); + + switch (format) { + case RTGFMT_8BIT: + case RTGFMT_RGB32: + EndShaderMode(); + break; + } + + if (mouse_cursor_enabled) { + float mc_x = mouse_cursor_x - rtg_offset_x; + float mc_y = mouse_cursor_y - rtg_offset_y; + Rectangle cursor_srcrect = { 0, 0, mouse_cursor_w, mouse_cursor_h }; + Rectangle dstrect = { mc_x * scale_x, mc_y * scale_y, (float)mouse_cursor_w * scale_x, (float)mouse_cursor_h * scale_y }; + DrawTexturePro(raylib_cursor_texture, cursor_srcrect, dstrect, origin, 0.0f, RAYWHITE); + } + + if (debug_palette) { + if (format == RTGFMT_8BIT) { + Rectangle srcrect = { 0, 0, 256, 1 }; + Rectangle dstrect = { 0, 0, 1024, 8 }; + DrawTexturePro(raylib_clut_texture, srcrect, dstrect, origin, 0.0f, RAYWHITE); + } + } + + if (show_fps) { + DrawFPS(GetScreenWidth() - 128, 0); + } + + EndDrawing(); + rtg_output_in_vblank = 1; + cur_rtg_frame++; + if (format == RTGFMT_RBG565) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + ((uint16_t *)indexed_buf)[x + (y * width)] = be16toh(((uint16_t *)data->memory)[(*data->addr / 2) + x + (y * (pitch / 2))]); + } + } + UpdateTexture(raylib_texture, indexed_buf); + } + else { + UpdateTexture(raylib_texture, &data->memory[*data->addr]); + } + if (cursor_image_updated) { + UpdateTexture(raylib_cursor_texture, cursor_data); + cursor_image_updated = 0; + } + updating_screen = 0; + } else { + BeginDrawing(); + ClearBackground(bef); + DrawText("RTG is currently sleeping.", 16, 16, 12, RAYWHITE); + EndDrawing(); + } + if (pitch != *data->pitch || height != *data->height || width != *data->width || format != *data->format) { + printf("Reinitializing due to something change.\n"); + reinit = 1; + goto shutdown_raylib; + } + if (emulator_exiting) { + goto shutdown_raylib; + } + } + + rtg_initialized = 0; + printf("RTG thread shut down.\n"); + +shutdown_raylib:; + + if (reinit) + goto reinit_raylib; + + if (indexed_buf) + free(indexed_buf); + + UnloadTexture(raylib_texture); + UnloadShader(clut_shader); + + CloseWindow(); + + return args; +} + +void rtg_set_clut_entry(uint8_t index, uint32_t xrgb) { + //palette[index] = xrgb; + unsigned char *src = (unsigned char *)&xrgb; + unsigned char *dst = (unsigned char *)&palette[index]; + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + dst[3] = 0xFF; +} + +void rtg_init_display() { + int err; + rtg_on = 1; + + if (!rtg_initialized) { + err = pthread_create(&thread_id, NULL, &rtgThread, (void *)&rtg_share_data); + if (err != 0) { + rtg_on = 0; + display_enabled = 0xFF; + printf("can't create RTG thread :[%s]", strerror(err)); + } + else { + rtg_initialized = 1; + pthread_setname_np(thread_id, "pistorm: rtg"); + printf("RTG Thread created successfully\n"); + } + } + printf("RTG display enabled.\n"); +} + +void rtg_shutdown_display() { + printf("RTG display disabled.\n"); + rtg_on = 0; + display_enabled = 0xFF; +} + +void rtg_enable_mouse_cursor() { + mouse_cursor_enabled = 1; +} + +void rtg_set_mouse_cursor_pos(int16_t x, int16_t y) { + mouse_cursor_x = x; + mouse_cursor_y = y; + //printf("Set mouse cursor pos to %d, %d.\n", x, y); +} + +static uint8_t clut_cursor_data[256*256]; + +void update_mouse_cursor(uint8_t *src) { + if (src != NULL) { + memset(clut_cursor_data, 0x00, 256*256); + uint8_t cur_bit = 0x80; + uint8_t line_pitch = (mouse_cursor_w / 8) * 2; + + for (uint8_t y = 0; y < mouse_cursor_h; y++) { + for (uint8_t x = 0; x < mouse_cursor_w; x++) { + if (src[(x / 8) + (line_pitch * y)] & cur_bit) + clut_cursor_data[x + (y * 256)] |= 0x01; + if (src[(x / 8) + (line_pitch * y) + (mouse_cursor_w / 8)] & cur_bit) + clut_cursor_data[x + (y * 256)] |= 0x02; + cur_bit >>= 1; + if (cur_bit == 0x00) + cur_bit = 0x80; + } + cur_bit = 0x80; + } + } + + for (int y = 0; y < mouse_cursor_h; y++) { + for (int x = 0; x < mouse_cursor_w; x++) { + cursor_data[x + (y * 256)] = cursor_palette[clut_cursor_data[x + (y * 256)]]; + } + } + + while (rtg_on && !updating_screen) + usleep(0); + cursor_image_updated = 1; +} + +void rtg_set_cursor_clut_entry(uint8_t r, uint8_t g, uint8_t b, uint8_t idx) { + uint32_t color = 0; + unsigned char *dst = (unsigned char *)&color; + + dst[0] = r; + dst[1] = g; + dst[2] = b; + dst[3] = 0xFF; + if (cursor_palette[idx + 1] != color) { + cursor_palette[0] = 0; + cursor_palette[idx + 1] = color; + update_mouse_cursor(NULL); + } +} + +static uint8_t old_mouse_w, old_mouse_h; +static uint8_t old_mouse_data[256]; + +void rtg_set_mouse_cursor_image(uint8_t *src, uint8_t w, uint8_t h) { + uint8_t new_cursor_data = 0; + + mouse_cursor_w = w; + mouse_cursor_h = h; + + if (memcmp(src, old_mouse_data, (w / 8 * h)) != 0) + new_cursor_data = 1; + + if (old_mouse_w != w || old_mouse_h != h || new_cursor_data) { + old_mouse_w = w; + old_mouse_h = h; + update_mouse_cursor(src); + } +} + +void rtg_show_fps(uint8_t enable) { + show_fps = (enable != 0); +} + +void rtg_palette_debug(uint8_t enable) { + debug_palette = (enable != 0); +} diff --git a/platforms/amiga/rtg/rtg-output.c b/platforms/amiga/rtg/rtg-output-sdl2.c similarity index 100% rename from platforms/amiga/rtg/rtg-output.c rename to platforms/amiga/rtg/rtg-output-sdl2.c diff --git a/platforms/amiga/rtg/rtg.c b/platforms/amiga/rtg/rtg.c index 6a1963a..80c7538 100644 --- a/platforms/amiga/rtg/rtg.c +++ b/platforms/amiga/rtg/rtg.c @@ -6,8 +6,14 @@ #include #include #include -#include "rtg.h" #include "config_file/config_file.h" +#include "gpio/ps_protocol.h" +#include "platforms/amiga/rtg/irtg_structs.h" +#include "rtg.h" + +#include "m68k.h" + +void rtg_p2c_ex(int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t h, uint8_t minterm, struct BitMap *bm, uint8_t mask, uint16_t dst_pitch, uint16_t src_pitch); uint8_t rtg_u8[4]; uint16_t rtg_x[8], rtg_y[8]; @@ -17,7 +23,7 @@ uint32_t rtg_address[8]; uint32_t rtg_address_adj[8]; uint32_t rtg_rgb[8]; -static uint8_t display_enabled = 0xFF; +uint8_t display_enabled = 0xFF; uint16_t rtg_display_width, rtg_display_height; uint16_t rtg_display_format; @@ -30,17 +36,22 @@ uint32_t framebuffer_addr = 0; uint32_t framebuffer_addr_adj = 0; static void handle_rtg_command(uint32_t cmd); -//static struct timespec f1, f2; +static void handle_irtg_command(uint32_t cmd); uint8_t realtime_graphics_debug = 0; extern int cpu_emulation_running; -/* -static const char *op_type_names[OP_TYPE_NUM] = { +extern struct emulator_config *cfg; +extern uint8_t rtg_on, rtg_enabled, rtg_output_in_vblank; + +//#define DEBUG_RTG + +#ifdef DEBUG_RTG +/*static const char *op_type_names[OP_TYPE_NUM] = { "BYTE", "WORD", "LONGWORD", "MEM", -}; +};*/ static const char *rtg_format_names[RTGFMT_NUM] = { "8BPP CLUT", @@ -48,25 +59,45 @@ static const char *rtg_format_names[RTGFMT_NUM] = { "32BPP RGB (RGBA)", "15BPP RGB (555)", }; -*/ -int init_rtg_data() { +#define DEBUG printf +#else +#define DEBUG(...) +#endif + +int init_rtg_data(struct emulator_config *cfg_) { rtg_mem = calloc(1, 40 * SIZE_MEGA); if (!rtg_mem) { printf("Failed to allocate RTG video memory.\n"); return 0; } + m68k_add_ram_range(PIGFX_RTG_BASE + PIGFX_REG_SIZE, 32 * SIZE_MEGA - PIGFX_REG_SIZE, rtg_mem); + add_mapping(cfg_, MAPTYPE_RAM_NOALLOC, PIGFX_RTG_BASE + PIGFX_REG_SIZE, 40 * SIZE_MEGA - PIGFX_REG_SIZE, -1, (char *)rtg_mem, "rtg_mem", 0); return 1; } -//extern uint8_t busy, rtg_on; -//void rtg_update_screen(); +void shutdown_rtg() { + printf("[RTG] Shutting down RTG.\n"); + if (rtg_on) { + display_enabled = 0xFF; + rtg_on = 0; + } + if (rtg_mem) { + free(rtg_mem); + rtg_mem = NULL; + } +} + +unsigned int rtg_get_fb() { + return PIGFX_RTG_BASE + PIGFX_REG_SIZE + framebuffer_addr_adj; +} + +uint8_t wait_vblank = 0; +uint32_t wait_rtg_frame = 0; +extern uint32_t cur_rtg_frame; unsigned int rtg_read(uint32_t address, uint8_t mode) { //printf("%s read from RTG: %.8X\n", op_type_names[mode], address); - if (address == RTG_COMMAND) { - return 0xFFCF; - } if (address >= PIGFX_REG_SIZE) { if (rtg_mem && (address - PIGFX_REG_SIZE) < PIGFX_UPPER) { switch (mode) { @@ -84,6 +115,33 @@ unsigned int rtg_read(uint32_t address, uint8_t mode) { } } } + switch (address) { + case RTG_COMMAND: + return rtg_enabled ? 0xFFCF : 0x0000; + case RTG_WAITVSYNC: + if (rtg_on) { + if (!wait_vblank && cur_rtg_frame != wait_rtg_frame) { + wait_rtg_frame = cur_rtg_frame; + if (wait_rtg_frame == 0) { + wait_rtg_frame = cur_rtg_frame; + } + if (wait_rtg_frame == 0) + printf("Wait RTG frame was zero!\n"); + wait_vblank = 1; + } + if (cur_rtg_frame != wait_rtg_frame && wait_vblank) { + wait_vblank = 0; + return 1; + } + else + return 0; + } + // fallthrough + case RTG_INVBLANK: + return !rtg_on || rtg_output_in_vblank; + default: + break; + } return 0; } @@ -124,8 +182,9 @@ void rtg_write(uint32_t address, uint32_t value, uint8_t mode) { return; } } - } - else { + } else if (address == RTG_DEBUGME) { + printf("RTG DEBUGME WRITE: %d.\n", value); + } else { switch (mode) { case OP_TYPE_BYTE: switch (address) { @@ -153,6 +212,9 @@ void rtg_write(uint32_t address, uint32_t value, uint8_t mode) { case RTG_COMMAND: handle_rtg_command(value); break; + case IRTG_COMMAND: + handle_irtg_command(value); + break; } break; case OP_TYPE_LONGWORD: @@ -178,11 +240,278 @@ void rtg_write(uint32_t address, uint32_t value, uint8_t mode) { } #define gdebug(a) if (realtime_graphics_debug) { printf(a); m68k_end_timeslice(); cpu_emulation_running = 0; } +#define M68KR(a) m68k_get_reg(NULL, a) +#define RGBF_D7 rgbf_to_rtg[M68KR(M68K_REG_D7)] +#define CMD_PITCH be16toh(r->BytesPerRow) + +static struct P96RenderInfo *r; +static struct P96BoardInfo *b; +static struct P96Line *ln; +static uint8_t cmd_mask; + +static void handle_irtg_command(uint32_t cmd) { + b = (struct P96BoardInfo *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A0)); + r = (struct P96RenderInfo *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A1)); + + switch (cmd) { + case RTGCMD_SETPAN: { + // A0: struct BoardInfo *b, A1: UBYTE *addr, D0 UWORD width, D1: WORD x_offset, D2: WORD y_offset, D7: RGBFTYPE format +#ifdef DEBUG_RTG + if (realtime_graphics_debug) { + printf("iSetPanning begin\n"); + printf("IRTGCmd SetPanning\n"); + printf("IRTGCmd x: %d y: %d w: %d (%d)\n", M68KR(M68K_REG_D1), M68KR(M68K_REG_D2), M68KR(M68K_REG_D0) << RGBF_D7, M68KR(M68K_REG_D0)); + printf("BoardInfo: %.8X Addr: %.8X\n", M68KR(M68K_REG_A0), M68KR(M68K_REG_A1)); + printf("BoardInfo Xoffs: %d Yoffs: %d\n", be16toh(b->XOffset), be16toh(b->YOffset)); + } +#endif + if (!b) break; + + b->XOffset = (int16_t)htobe16(M68KR(M68K_REG_D1)); + b->YOffset = (int16_t)htobe16(M68KR(M68K_REG_D2)); + + rtg_offset_x = M68KR(M68K_REG_D1); + rtg_offset_y = M68KR(M68K_REG_D2); + rtg_pitch = (M68KR(M68K_REG_D0) << RGBF_D7); + framebuffer_addr = M68KR(M68K_REG_A1) - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + framebuffer_addr_adj = framebuffer_addr + (rtg_offset_x << RGBF_D7) + (rtg_offset_y * rtg_pitch); + +#ifdef DEBUG_RTG + if (realtime_graphics_debug) { + printf("RTG OffsetX/Y: %d/%d\n", rtg_offset_x, rtg_offset_y); + printf("RTG Pitch: %d\n", rtg_pitch); + printf("RTG FBAddr/Adj: %.8X (%.8X)/%.8X\n", framebuffer_addr, M68KR(M68K_REG_A1), framebuffer_addr_adj); + printf("iSetPanning End\n"); + } +#endif + + break; + } + case RTGCMD_DRAWLINE: { + // A0: struct BoardInfo *b, A1: RenderInfo *r A2: struct Line *l, D0: UBYTE mask, D7: RGBFTYPE format + gdebug("iDrawLine begin\n"); + ln = (struct P96Line *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A2)); + + if (!ln || !r) break; + + cmd_mask = (uint8_t)M68KR(M68K_REG_D0); + rtg_address_adj[0] = be32toh(r->_p_Memory) - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + + if (cmd_mask == 0xFF && be16toh(ln->LinePtrn) == 0xFFFF) { + rtg_drawline_solid(be16toh(ln->X), be16toh(ln->Y), be16toh(ln->dX), be16toh(ln->dY), + be16toh(ln->Length), be32toh(ln->FgPen), CMD_PITCH, RGBF_D7); + } else { + rtg_drawline(be16toh(ln->X), be16toh(ln->Y), be16toh(ln->dX), be16toh(ln->dY), + be16toh(ln->Length), be16toh(ln->LinePtrn), be16toh(ln->PatternShift), + be32toh(ln->FgPen), be32toh(ln->BgPen), CMD_PITCH, + RGBF_D7, cmd_mask, ln->DrawMode); + } + gdebug("iDrawLine end\n"); + break; + } + case RTGCMD_FILLRECT: { + // A0: BoardInfo *b, A1: RenderInfo *r + // D0 WORD x, D1: WORD y, D2: WORD w, D3: WORD h + // D4: ULONG color, D5: UBYTE mask, D7: RGBFTYPE format + gdebug("iFillRect begin\n"); +#ifdef DEBUG_RTG + if (realtime_graphics_debug) { + DEBUG("X1/X2: %d/%d-> X2/Y2: %d/%d\n", (int16_t)M68KR(M68K_REG_D0), (int16_t)M68KR(M68K_REG_D1), (int16_t)M68KR(M68K_REG_D2), (int16_t)M68KR(M68K_REG_D3)); + DEBUG("R: %.8X B: %.8X\n", M68KR(M68K_REG_A0), M68KR(M68K_REG_A1)); + } +#endif + + if (!b || !r) break; + + cmd_mask = (uint8_t)M68KR(M68K_REG_D5); + rtg_address_adj[0] = be32toh(r->_p_Memory) - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + + if (cmd_mask == 0xFF) { + rtg_fillrect_solid((int16_t)M68KR(M68K_REG_D0), (int16_t)M68KR(M68K_REG_D1), (int16_t)M68KR(M68K_REG_D2), (int16_t)M68KR(M68K_REG_D3), + M68KR(M68K_REG_D4), CMD_PITCH, RGBF_D7); + } else { + rtg_fillrect((int16_t)M68KR(M68K_REG_D0), (int16_t)M68KR(M68K_REG_D1), (int16_t)M68KR(M68K_REG_D2), (int16_t)M68KR(M68K_REG_D3), + M68KR(M68K_REG_D4), CMD_PITCH, RGBF_D7, cmd_mask); + } + gdebug("iFillRect end\n"); + break; + } + case RTGCMD_INVERTRECT: { + // A0: BoardInfo *b, A1: RenderInfo *r + // D0 WORD x, D1: WORD y, D2: WORD w, D3: WORD h + // D4: UBYTE mask, D7: RGBFTYPE format + gdebug("iInvertRect begin\n"); + if (!b || !r) break; + + cmd_mask = (uint8_t)M68KR(M68K_REG_D4); + rtg_address_adj[0] = be32toh(r->_p_Memory) - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + + rtg_invertrect((int16_t)M68KR(M68K_REG_D0), (int16_t)M68KR(M68K_REG_D1), (int16_t)M68KR(M68K_REG_D2), (int16_t)M68KR(M68K_REG_D3), CMD_PITCH, RGBF_D7, cmd_mask); + gdebug("iInvertRect end\n"); + break; + } + case RTGCMD_BLITRECT: { + // A0: BoardInfo *b, A1: RenderInfo *r) + // D0: WORD x, D1: WORD y, D2: WORD dx, D3: WORD dy, D4: WORD w, D5: WORD h, + // D6: UBYTE mask, D7: RGBFTYPE format + gdebug("iBlitRect begin\n"); + + cmd_mask = (uint8_t)M68KR(M68K_REG_D6); + rtg_address_adj[0] = be32toh(r->_p_Memory) - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + + if (cmd_mask == 0xFF) { + rtg_blitrect_solid(M68KR(M68K_REG_D0), M68KR(M68K_REG_D1), M68KR(M68K_REG_D2), M68KR(M68K_REG_D3), M68KR(M68K_REG_D4), M68KR(M68K_REG_D5), CMD_PITCH, RGBF_D7); + } else { + rtg_blitrect(M68KR(M68K_REG_D0), M68KR(M68K_REG_D1), M68KR(M68K_REG_D2), M68KR(M68K_REG_D3), M68KR(M68K_REG_D4), M68KR(M68K_REG_D5), CMD_PITCH, RGBF_D7, cmd_mask); + } + + gdebug("iBlitRect end\n"); + break; + } + case RTGCMD_BLITRECT_NOMASK_COMPLETE: { + // A0: BoardInfo *b, A1: RenderInfo *rs, A2: RenderInfo *rt, + // D0: WORD x, D1: WORD y, D2: WORD dx, D3: WORD dy, D4: WORD w, D5: WORD h, + // D6: UBYTE minterm, D7: RGBFTYPE format + gdebug("iBlitRectNoMaskComplete begin\n"); + + uint8_t minterm = (uint8_t)M68KR(M68K_REG_D6); + struct P96RenderInfo *rt = (struct P96RenderInfo *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A2)); + + uint32_t src_addr = be32toh(r->_p_Memory); + uint32_t dst_addr = be32toh(rt->_p_Memory); + + rtg_blitrect_nomask_complete(M68KR(M68K_REG_D0), M68KR(M68K_REG_D1), M68KR(M68K_REG_D2), M68KR(M68K_REG_D3), M68KR(M68K_REG_D4), M68KR(M68K_REG_D5), + CMD_PITCH, be16toh(rt->BytesPerRow), src_addr, dst_addr, RGBF_D7, minterm); + + gdebug("iBlitRectNoMaskComplete end\n"); + break; + } + case RTGCMD_BLITTEMPLATE: { + // A0: BoardInfo *b, A1: RenderInfo *r, A2: Template *t + // D0: WORD x, D1: WORD y, D2: WORD w, D3: WORD h + // D4: UBYTE mask, D7: RGBFTYPE format + if (!r || !M68KR(M68K_REG_A2)) + break; + gdebug("iBlitTemplate begin\n"); + + uint16_t t_pitch = 0, x_offset = 0; + uint32_t src_addr = M68KR(M68K_REG_A2); + uint32_t fgcol = 0, bgcol = 0; + uint8_t draw_mode = 0; + + struct P96Template *t = (struct P96Template *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A2)); + if (t) { + t_pitch = be16toh(t->BytesPerRow); + fgcol = be32toh(t->FgPen); + bgcol = be32toh(t->BgPen); + x_offset = t->XOffset; + draw_mode = t->DrawMode; + src_addr = be32toh(t->_p_Memory); + } else { + t_pitch = be16toh(ps_read_16(src_addr + (uint32_t)&t->BytesPerRow)); + fgcol = be32toh(ps_read_32(src_addr + (uint32_t)&t->FgPen)); + bgcol = be32toh(ps_read_32(src_addr + (uint32_t)&t->BgPen)); + x_offset = ps_read_8(src_addr + (uint32_t)&t->XOffset); + draw_mode = ps_read_8(src_addr + (uint32_t)&t->DrawMode); + src_addr = be32toh(ps_read_32(src_addr + (uint32_t)&t->_p_Memory)); + } + + cmd_mask = (uint8_t)M68KR(M68K_REG_D4); + rtg_address[1] = be32toh(r->_p_Memory); + rtg_address_adj[1] = rtg_address[1] - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + + rtg_blittemplate(M68KR(M68K_REG_D0), M68KR(M68K_REG_D1), M68KR(M68K_REG_D2), M68KR(M68K_REG_D3), src_addr, fgcol, bgcol, CMD_PITCH, t_pitch, RGBF_D7, x_offset, cmd_mask, draw_mode); + gdebug("iBlitTemplate end\n"); + break; + } + case RTGCMD_BLITPATTERN: { + // A0: BoardInfo *b, A1: RenderInfo *r, A2: Pattern *p + // D0: WORD x, D1: WORD y, D2: WORD w, D3: WORD h + // D4: UBYTE mask, D7: RGBFTYPE format + if (!r || !M68KR(M68K_REG_A2)) + break; + gdebug("iBlitPattern begin\n"); + + uint16_t x_offset = 0, y_offset = 0; + uint32_t src_addr = M68KR(M68K_REG_A2); + uint32_t fgcol = 0, bgcol = 0; + uint8_t draw_mode = 0, loop_rows = 0; + + struct P96Pattern *p = (struct P96Pattern *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A2)); + if (p) { + fgcol = be32toh(p->FgPen); + bgcol = be32toh(p->BgPen); + x_offset = be16toh(p->XOffset); + y_offset = be16toh(p->YOffset); + draw_mode = p->DrawMode; + loop_rows = 1 << p->Size; + src_addr = be32toh(p->_p_Memory); + } else { + fgcol = be32toh(ps_read_32(src_addr + (uint32_t)&p->FgPen)); + bgcol = be32toh(ps_read_32(src_addr + (uint32_t)&p->BgPen)); + x_offset = be16toh(ps_read_16(src_addr + (uint32_t)&p->XOffset)); + y_offset = be16toh(ps_read_16(src_addr + (uint32_t)&p->YOffset)); + draw_mode = ps_read_8(src_addr + (uint32_t)&p->DrawMode); + loop_rows = 1 << ps_read_8(src_addr + (uint32_t)&p->Size); + src_addr = be32toh(p->_p_Memory); + } + + cmd_mask = (uint8_t)M68KR(M68K_REG_D4); + rtg_address[1] = be32toh(r->_p_Memory); + rtg_address_adj[1] = rtg_address[1] - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + + rtg_blitpattern(M68KR(M68K_REG_D0), M68KR(M68K_REG_D1), M68KR(M68K_REG_D2), M68KR(M68K_REG_D3), src_addr, fgcol, bgcol, CMD_PITCH, RGBF_D7, x_offset, y_offset, cmd_mask, draw_mode, loop_rows); + gdebug("iBlitPattern end\n"); + break; + } + case RTGCMD_P2C: { + // A0: BoardInfo, A1: BitMap *bm, A2: RenderInfo *r, + // D0: SHORT x, D1: SHORT y, D2: SHORT dx, D3: SHORT dy, D4: SHORT w, D5: SHORT h, + // D6: UBYTE minterm, D7: UBYTE mask + r = (struct P96RenderInfo *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A2)); + struct BitMap *bm = (struct BitMap *)get_mapped_data_pointer_by_address(cfg, M68KR(M68K_REG_A1)); + if (!r || !bm) + break; + + gdebug("iP2C begin\n"); + + if (!bm) { + printf ("Help! BitMap not in mapped memory.\n"); + break; + } else { + gdebug("Data is available in mapped memory.\n"); + } + + if (realtime_graphics_debug) { + printf("bm: %.8X r: %.8X\n", (uint32_t)bm, (uint32_t)r); + if (bm) + printf("bm pitch: %d\n", be16toh(bm->BytesPerRow)); + if (r) + printf("r pitch: %d\n", be16toh(r->BytesPerRow)); + } + + uint16_t bmp_pitch = be16toh(bm->BytesPerRow); + uint16_t line_pitch = be16toh(r->BytesPerRow); + rtg_address_adj[0] = be32toh(r->_p_Memory) - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); + + uint8_t minterm = (uint8_t)M68KR(M68K_REG_D6); + cmd_mask = (uint8_t)M68KR(M68K_REG_D7); + + rtg_p2c_ex(M68KR(M68K_REG_D0), M68KR(M68K_REG_D1), M68KR(M68K_REG_D2), M68KR(M68K_REG_D3), M68KR(M68K_REG_D4), M68KR(M68K_REG_D5), minterm, bm, cmd_mask, line_pitch, bmp_pitch); + gdebug("iP2C end\n"); + break; + } + default: + printf("[!!!IRTG] Unnkonw/unhandled iRTG command %d.\n", cmd); + break; + } +} static void handle_rtg_command(uint32_t cmd) { - //printf("Handling RTG command %d (%.8X)\n", cmd, cmd); + //printf("Handling RTG command %d (%.8X)\n", cmd, cmd); switch (cmd) { case RTGCMD_SETGC: + gdebug("SetGC\n"); rtg_display_format = rtg_format; rtg_display_width = rtg_x[0]; rtg_display_height = rtg_y[0]; @@ -196,9 +525,13 @@ static void handle_rtg_command(uint32_t cmd) { framebuffer_addr_adj = framebuffer_addr + (rtg_offset_x << rtg_display_format) + (rtg_offset_y * rtg_pitch); rtg_total_rows = rtg_y[1]; } - //printf("Set RTG mode:\n"); - //printf("%dx%d pixels\n", rtg_display_width, rtg_display_height); - //printf("Pixel format: %s\n", rtg_format_names[rtg_display_format]); + if (realtime_graphics_debug) { + printf("Set RTG mode:\n"); + printf("%dx%d pixels\n", rtg_display_width, rtg_display_height); +#ifdef DEBUG_RTG + printf("Pixel format: %s\n", rtg_format_names[rtg_display_format]); +#endif + } break; case RTGCMD_SETPAN: //printf("Command: SetPan.\n"); @@ -207,10 +540,6 @@ static void handle_rtg_command(uint32_t cmd) { rtg_pitch = (rtg_x[0] << rtg_display_format); framebuffer_addr = rtg_address[0] - (PIGFX_RTG_BASE + PIGFX_REG_SIZE); framebuffer_addr_adj = framebuffer_addr + (rtg_offset_x << rtg_display_format) + (rtg_offset_y * rtg_pitch); - //printf("Set panning to $%.8X (%.8X)\n", framebuffer_addr, rtg_address[0]); - //printf("(Panned: $%.8X)\n", framebuffer_addr_adj); - //printf("Offset X/Y: %d/%d\n", rtg_offset_x, rtg_offset_y); - printf("Pitch: %d (%d bytes)\n", rtg_x[0], rtg_pitch); break; case RTGCMD_SETCLUT: { //printf("Command: SetCLUT.\n"); @@ -220,19 +549,23 @@ static void handle_rtg_command(uint32_t cmd) { break; } case RTGCMD_SETDISPLAY: - //printf("RTG SetDisplay %s\n", (rtg_u8[1]) ? "enabled" : "disabled"); - // I remeber wrongs. - //printf("Command: SetDisplay.\n"); + gdebug("SetDisplay\n"); + if (realtime_graphics_debug) { + printf("RTG SetDisplay %s\n", (rtg_u8[1]) ? "enabled" : "disabled"); + } break; case RTGCMD_ENABLE: case RTGCMD_SETSWITCH: - //printf("RTG SetSwitch %s\n", ((rtg_x[0]) & 0x01) ? "enabled" : "disabled"); - //printf("LAL: %.4X\n", rtg_x[0]); - if (display_enabled != ((rtg_x[0]) & 0x01)) { - display_enabled = ((rtg_x[0]) & 0x01); - if (display_enabled) { + gdebug("SetSwitch\n"); + if (realtime_graphics_debug) { + printf("RTG SetSwitch %s\n", ((rtg_x[0]) & 0x01) ? "enabled" : "disabled"); + printf("LAL: %.4X\n", rtg_x[0]); + } + display_enabled = ((rtg_x[0]) & 0x01); + if (display_enabled != rtg_on) { + rtg_on = display_enabled; + if (rtg_on) rtg_init_display(); - } else rtg_shutdown_display(); } @@ -277,15 +610,38 @@ static void handle_rtg_command(uint32_t cmd) { if (rtg_u8[0] == 0xFF && rtg_y[2] == 0xFFFF) rtg_drawline_solid(rtg_x[0], rtg_y[0], rtg_x[1], rtg_y[1], rtg_x[2], rtg_rgb[0], rtg_x[3], rtg_format); else - rtg_drawline(rtg_x[0], rtg_y[0], rtg_x[1], rtg_y[1], rtg_x[2], rtg_y[2], rtg_x[4], rtg_rgb[0], rtg_rgb[1], rtg_x[3], rtg_format, rtg_u8[0], rtg_u8[1]); + rtg_drawline(rtg_x[0], rtg_y[0], rtg_x[1], rtg_y[1], rtg_x[2], rtg_y[2], rtg_x[4], rtg_rgb[0], rtg_rgb[1], rtg_x[3], rtg_format, rtg_u8[0], rtg_u8[1]); gdebug("DrawLine\n"); break; case RTGCMD_P2C: rtg_p2c(rtg_x[0], rtg_y[0], rtg_x[1], rtg_y[1], rtg_x[2], rtg_y[2], rtg_u8[1], rtg_u8[2], rtg_u8[0], (rtg_user[0] >> 0x8), rtg_x[4], (uint8_t *)&rtg_mem[rtg_address_adj[1]]); - //rtg_p2c_broken(rtg_x[0], rtg_y[0], rtg_x[1], rtg_y[1], rtg_x[2], rtg_y[2], rtg_x[3], rtg_u8[0], rtg_u8[1], rtg_u8[2], rtg_user[0]); gdebug("Planar2Chunky\n"); break; case RTGCMD_P2D: + rtg_p2d(rtg_x[0], rtg_y[0], rtg_x[1], rtg_y[1], rtg_x[2], rtg_y[2], rtg_u8[1], rtg_u8[2], rtg_u8[0], (rtg_user[0] >> 0x8), rtg_x[4], (uint8_t *)&rtg_mem[rtg_address_adj[1]]); + gdebug("Planar2Direct\n"); + break; + case RTGCMD_SETSPRITE: + rtg_enable_mouse_cursor(); + gdebug("SetSprite\n"); + break; + case RTGCMD_SETSPRITECOLOR: + rtg_set_cursor_clut_entry(rtg_u8[0], rtg_u8[1], rtg_u8[2], rtg_u8[3]); + gdebug("SetSpriteColor\n"); + break; + case RTGCMD_SETSPRITEPOS: + rtg_set_mouse_cursor_pos((int16_t)rtg_x[0], (int16_t)rtg_y[0]); + gdebug("SetSpritePos\n"); + break; + case RTGCMD_SETSPRITEIMAGE: + rtg_set_mouse_cursor_image(&rtg_mem[rtg_address_adj[1]], rtg_u8[0], rtg_u8[1]); + gdebug("SetSpriteImage\n"); + break; + case RTGCMD_DEBUGME: + printf ("[RTG] DebugMe!\n"); + break; + default: + printf("[!!!RTG] Unknown/unhandled RTG command %d ($%.4X)\n", cmd, cmd); break; } } diff --git a/platforms/amiga/rtg/rtg.h b/platforms/amiga/rtg/rtg.h index f8bdf11..63decc7 100644 --- a/platforms/amiga/rtg/rtg.h +++ b/platforms/amiga/rtg/rtg.h @@ -9,13 +9,25 @@ #define CARD_OFFSET 0 -#include "rtg_driver_amiga/rtg_enums.h" +#include "rtg_enums.h" void rtg_write(uint32_t address, uint32_t value, uint8_t mode); unsigned int rtg_read(uint32_t address, uint8_t mode); void rtg_set_clut_entry(uint8_t index, uint32_t xrgb); void rtg_init_display(); void rtg_shutdown_display(); +void rtg_enable_mouse_cursor(); + +unsigned int rtg_get_fb(); +void rtg_set_mouse_cursor_pos(int16_t x, int16_t y); +void rtg_set_cursor_clut_entry(uint8_t r, uint8_t g, uint8_t b, uint8_t idx); +void rtg_set_mouse_cursor_image(uint8_t *src, uint8_t w, uint8_t h); + +void rtg_show_fps(uint8_t enable); +void rtg_palette_debug(uint8_t enable); + +int init_rtg_data(struct emulator_config *cfg); +void shutdown_rtg(); void rtg_fillrect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t color, uint16_t pitch, uint16_t format, uint8_t mask); void rtg_fillrect_solid(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t color, uint16_t pitch, uint16_t format); @@ -29,25 +41,34 @@ void rtg_drawline_solid(int16_t x1_, int16_t y1_, int16_t x2_, int16_t y2_, uint void rtg_drawline (int16_t x1_, int16_t y1_, int16_t x2_, int16_t y2_, uint16_t len, uint16_t pattern, uint16_t pattern_offset, uint32_t fgcol, uint32_t bgcol, uint16_t pitch, uint16_t format, uint8_t mask, uint8_t draw_mode); void rtg_p2c (int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t h, uint8_t draw_mode, uint8_t planes, uint8_t mask, uint8_t layer_mask, uint16_t src_line_pitch, uint8_t *bmp_data_src); +void rtg_p2d (int16_t sx, int16_t sy, int16_t dx, int16_t dy, int16_t w, int16_t h, uint8_t draw_mode, uint8_t planes, uint8_t mask, uint8_t layer_mask, uint16_t src_line_pitch, uint8_t *bmp_data_src); #define PATTERN_LOOPX \ - tmpl_x ^= 0x01; \ - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; \ + if (sptr) { cur_byte = sptr[tmpl_x]; } \ + else { cur_byte = m68k_read_memory_8(src_addr + tmpl_x); } \ + if (invert) { cur_byte ^= 0xFF; } \ + tmpl_x ^= 0x01; #define PATTERN_LOOPY \ sptr += 2 ; \ - if ((ys + offset_y + 1) % loop_rows == 0) \ - sptr = sptr_base; \ + src_addr += 2; \ + if ((ys + offset_y + 1) % loop_rows == 0) { \ + if (sptr) sptr = sptr_base; \ + src_addr = src_addr_base; \ + } \ tmpl_x = (offset_x / 8) % 2; \ cur_bit = base_bit; \ dptr += pitch; #define TEMPLATE_LOOPX \ - tmpl_x++; \ - cur_byte = (invert) ? sptr[tmpl_x] ^ 0xFF : sptr[tmpl_x]; \ + if (sptr) { cur_byte = sptr[tmpl_x]; } \ + else { cur_byte = m68k_read_memory_8(src_addr + tmpl_x); } \ + if (invert) { cur_byte ^= 0xFF; } \ + tmpl_x++; #define TEMPLATE_LOOPY \ - sptr += t_pitch; \ + if (sptr) sptr += t_pitch; \ + src_addr += t_pitch; \ dptr += pitch; \ tmpl_x = offset_x / 8; \ cur_bit = base_bit; diff --git a/platforms/amiga/rtg/rtg_driver_amiga/build.bat b/platforms/amiga/rtg/rtg_driver_amiga/build.bat deleted file mode 100644 index 1c6688e..0000000 --- a/platforms/amiga/rtg/rtg_driver_amiga/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -vc +aos68k -nostdlib -I$VBCC/targets/m68k-amigaos/include2 -c99 -O2 -o pigfx020.card pigfx.c -ldebug -lamiga -cpu=68020 -vc +aos68k -nostdlib -I$VBCC/targets/m68k-amigaos/include2 -c99 -O2 -o pigfx030.card pigfx.c -ldebug -lamiga -cpu=68030 diff --git a/platforms/amiga/rtg/rtg_driver_amiga/build.sh b/platforms/amiga/rtg/rtg_driver_amiga/build.sh index 1c6688e..8ae6895 100644 --- a/platforms/amiga/rtg/rtg_driver_amiga/build.sh +++ b/platforms/amiga/rtg/rtg_driver_amiga/build.sh @@ -1,2 +1,3 @@ -vc +aos68k -nostdlib -I$VBCC/targets/m68k-amigaos/include2 -c99 -O2 -o pigfx020.card pigfx.c -ldebug -lamiga -cpu=68020 -vc +aos68k -nostdlib -I$VBCC/targets/m68k-amigaos/include2 -c99 -O2 -o pigfx030.card pigfx.c -ldebug -lamiga -cpu=68030 +m68k-amigaos-gcc pigfx-2.c -m68020 -O2 -o pigfx020.card -noixemul -Wall -Wextra -Wno-unused-parameter -fomit-frame-pointer -nostartfiles -lamiga +m68k-amigaos-gcc pigfx-2.c -m68030 -O2 -o pigfx030.card -noixemul -Wall -Wextra -Wno-unused-parameter -fomit-frame-pointer -nostartfiles -lamiga +m68k-amigaos-gcc pigfx-2.c -m68020 -O2 -o pigfx020i.card -noixemul -Wall -Wextra -Wno-unused-parameter -fomit-frame-pointer -nostartfiles -lamiga -DIRTG diff --git a/platforms/amiga/rtg/rtg_driver_amiga/pigfx-2.c b/platforms/amiga/rtg/rtg_driver_amiga/pigfx-2.c new file mode 100644 index 0000000..6ac9229 --- /dev/null +++ b/platforms/amiga/rtg/rtg_driver_amiga/pigfx-2.c @@ -0,0 +1,878 @@ +// SPDX-License-Identifier: MIT + +// PiStorm RTG driver, VBCC edition. +// Based in part on the ZZ9000 RTG driver. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "boardinfo.h" +#include "../rtg_enums.h" + +#define STR(s) #s +#define XSTR(s) STR(s) + + +#define CHECKRTG *((unsigned short *)(CARD_OFFSET)) + +#define CARD_OFFSET 0x70000000 +#define IRTGCMD_OFFSET 0x70000060 +#define CARD_REGSIZE 0x00010000 +#define CARD_MEMSIZE 0x02000000 // 32MB "VRAM" +#define CARD_SCRATCH 0x72010000 + +#define WRITESHORT(cmd, val) *(unsigned short *)((unsigned long)(CARD_OFFSET+cmd)) = val; +#define WRITELONG(cmd, val) *(unsigned long *)((unsigned long)(CARD_OFFSET+cmd)) = val; +#define WRITEBYTE(cmd, val) *(unsigned char *)((unsigned long)(CARD_OFFSET+cmd)) = val; + +#define READSHORT(cmd, var) var = *(volatile unsigned short *)(CARD_OFFSET + cmd); +#define READLONG(cmd, var) var = *(volatile unsigned long *)(CARD_OFFSET + cmd); + +#define RTG_DEBUGME(val) *(volatile unsigned long *)((unsigned long)(CARD_OFFSET+RTG_DEBUGME)) = val; +//#define RTG_DEBUGME(...) +#define IWRITECMD(val) *(volatile unsigned short *)(IRTGCMD_OFFSET) = val; + +#define CHIP_RAM_SIZE 0x00200000 // Chip RAM offset, 2MB + +struct GFXBase { + struct Library libNode; + BPTR segList; + struct ExecBase* sysBase; + struct ExpansionBase* expansionBase; +}; + +#define __saveds__ +#define kprintf(...) + +struct ExecBase *SysBase; + +int FindCard(__REGA0(struct BoardInfo* b)); +int InitCard(__REGA0(struct BoardInfo* b)); + +void SetDAC (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)); +void SetGC (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(BOOL border)); +void SetColorArray (__REGA0(struct BoardInfo *b), __REGD0(UWORD start), __REGD1(UWORD num)); +void SetPanning (__REGA0(struct BoardInfo *b), __REGA1(UBYTE *addr), __REGD0(UWORD width), __REGD1(WORD x_offset), __REGD2(WORD y_offset), __REGD7(RGBFTYPE format)); +UWORD SetSwitch (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)); +UWORD SetDisplay (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)); + +UWORD CalculateBytesPerRow (__REGA0(struct BoardInfo *b), __REGD0(UWORD width), __REGD7(RGBFTYPE format)); +APTR CalculateMemory (__REGA0(struct BoardInfo *b), __REGA1(unsigned long addr), __REGD7(RGBFTYPE format)); +ULONG GetCompatibleFormats (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)); + +LONG ResolvePixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG pixel_clock), __REGD7(RGBFTYPE format)); +ULONG GetPixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG index), __REGD7(RGBFTYPE format)); +void SetClock (__REGA0(struct BoardInfo *b)); + +void SetMemoryMode (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)); +void SetWriteMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)); +void SetClearMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)); +void SetReadPlane (__REGA0(struct BoardInfo *b), __REGD0(UBYTE plane)); + +void WaitVerticalSync (__REGA0(struct BoardInfo *b), __REGD0(BOOL toggle)); +BOOL GetVSyncState(__REGA0(struct BoardInfo *b), __REGD0(BOOL toggle)); + +void FillRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(ULONG color), __REGD5(UBYTE mask), __REGD7(RGBFTYPE format)); +void InvertRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)); +void BlitRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE mask), __REGD7(RGBFTYPE format)); +void BlitRectNoMaskComplete (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *rs), __REGA2(struct RenderInfo *rt), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE minterm), __REGD7(RGBFTYPE format)); +void BlitTemplate (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Template *t), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)); +void BlitPattern (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Pattern *p), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)); +void DrawLine (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Line *l), __REGD0(UBYTE mask), __REGD7(RGBFTYPE format)); + +void BlitPlanar2Chunky (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bm), __REGA2(struct RenderInfo *r), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)); +void BlitPlanar2Direct (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bmp), __REGA2(struct RenderInfo *r), __REGA3(struct ColorIndexMapping *clut), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)); + +void SetSprite (__REGA0(struct BoardInfo *b), __REGD0(BOOL what), __REGD7(RGBFTYPE format)); +void SetSpritePosition (__REGA0(struct BoardInfo *b), __REGD0(WORD x), __REGD1(WORD y), __REGD7(RGBFTYPE format)); +void SetSpriteImage (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)); +void SetSpriteColor (__REGA0(struct BoardInfo *b), __REGD0(UBYTE idx), __REGD1(UBYTE R), __REGD2(UBYTE G), __REGD3(UBYTE B), __REGD7(RGBFTYPE format)); + +#define DEVICE_VERSION 43 +#define DEVICE_REVISION 20 +#define DEVICE_PRIORITY 0 +#define DEVICE_ID_STRING "PiGFX " XSTR(DEVICE_VERSION) "." XSTR(DEVICE_REVISION) " " DEVICE_DATE +#define DEVICE_NAME "pigfx.card" +#define DEVICE_DATE "(29 May 2021)" + + +int __attribute__((no_reorder)) _start() +{ + return -1; +} + +asm("romtag: \n" + " dc.w "XSTR(RTC_MATCHWORD)" \n" + " dc.l romtag \n" + " dc.l endcode \n" + " dc.b "XSTR(RTF_AUTOINIT)" \n" + " dc.b "XSTR(DEVICE_VERSION)" \n" + " dc.b "XSTR(NT_LIBRARY)" \n" + " dc.b "XSTR(DEVICE_PRIORITY)" \n" + " dc.l _device_name \n" + " dc.l _device_id_string \n" + " dc.l _auto_init_tables \n" + "endcode: \n"); + +char device_name[] = DEVICE_NAME; +char device_id_string[] = DEVICE_ID_STRING; + +__saveds struct GFXBase* OpenLib(__REGA6(struct GFXBase *gfxbase)); +BPTR __saveds CloseLib(__REGA6(struct GFXBase *gfxbase)); +BPTR __saveds ExpungeLib(__REGA6(struct GFXBase *exb)); +ULONG ExtFuncLib(void); +__saveds struct GFXBase* InitLib(__REGA6(struct ExecBase *sysbase), + __REGA0(BPTR seglist), + __REGD0(struct GFXBase *exb)); + +#define CLOCK_HZ 100000000 + +static struct GFXBase *_gfxbase; +const char *gfxname = "PiStorm RTG"; +char dummies[128]; + +__saveds struct GFXBase* __attribute__((used)) InitLib(__REGA6(struct ExecBase *sysbase), + __REGA0(BPTR seglist), + __REGD0(struct GFXBase *exb)) +{ + _gfxbase = exb; + SysBase = *(struct ExecBase **)4L; + return _gfxbase; +} + +__saveds struct GFXBase* __attribute__((used)) OpenLib(__REGA6(struct GFXBase *gfxbase)) +{ + gfxbase->libNode.lib_OpenCnt++; + gfxbase->libNode.lib_Flags &= ~LIBF_DELEXP; + + return gfxbase; +} + +BPTR __saveds __attribute__((used)) CloseLib(__REGA6(struct GFXBase *gfxbase)) +{ + gfxbase->libNode.lib_OpenCnt--; + + if (!gfxbase->libNode.lib_OpenCnt) { + if (gfxbase->libNode.lib_Flags & LIBF_DELEXP) { + return (ExpungeLib(gfxbase)); + } + } + return 0; +} + +BPTR __saveds __attribute__((used)) ExpungeLib(__REGA6(struct GFXBase *exb)) +{ + BPTR seglist; + struct ExecBase *SysBase = *(struct ExecBase **)4L; + + if(!exb->libNode.lib_OpenCnt) { + ULONG negsize, possize, fullsize; + UBYTE *negptr = (UBYTE *)exb; + + seglist = exb->segList; + + Remove((struct Node *)exb); + + negsize = exb->libNode.lib_NegSize; + possize = exb->libNode.lib_PosSize; + fullsize = negsize + possize; + negptr -= negsize; + + FreeMem(negptr, fullsize); + return(seglist); + } + + exb->libNode.lib_Flags |= LIBF_DELEXP; + return 0; +} + +ULONG ExtFuncLib(void) +{ + return 0; +} + +#define LOADLIB(a, b) if ((a = (struct a*)OpenLibrary((STRPTR)b,0L))==NULL) { \ + kprintf((STRPTR)"Failed to load %s.\n", b); \ + return 0; \ + } \ + + +int __attribute__((used)) FindCard(__REGA0(struct BoardInfo* b)) { + uint16_t card_check = CHECKRTG; + if (card_check != 0xFFCF) { + // RTG not enabled + return 0; + } + + struct IORequest io; + if (OpenDevice((STRPTR)"input.device", 0, &io, 0) == 0) + { + struct Library *InputBase = (struct Library *)io.io_Device; + UWORD qual = PeekQualifier(); + CloseDevice(&io); + + if (qual & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)) + return(FALSE); + } + + struct ExpansionBase *ExpansionBase = NULL; + struct DOSBase *DOSBase = NULL; + struct IntuitionBase *IntuitionBase = NULL; + + LOADLIB(ExpansionBase, "expansion.library"); + LOADLIB(DOSBase, "dos.library"); + LOADLIB(IntuitionBase, "intuition.library"); + + b->MemorySize = CARD_MEMSIZE; + b->RegisterBase = (void *)CARD_OFFSET; + b->MemoryBase = (void *)(CARD_OFFSET + CARD_REGSIZE); + + return 1; +} + +#define HWSPRITE 1 +#define VGASPLIT (1 << 6) +#define FLICKERFIXER (1 << 12) +#define INDISPLAYCHAIN (1 << 20) +#define DIRECTACCESS (1 << 26) + +int __attribute__((used)) InitCard(__REGA0(struct BoardInfo* b)) { + int i; + kprintf("Wueh! %ld\n", sizeof(BOOL)); + + b->CardBase = (struct CardBase *)_gfxbase; + b->ExecBase = SysBase; + b->BoardName = "PiStorm RTG"; + b->BoardType = 14; + b->PaletteChipType = PCT_S3ViRGE; + b->GraphicsControllerType = GCT_S3ViRGE; + + b->Flags = BIF_INDISPLAYCHAIN | BIF_GRANTDIRECTACCESS | BIF_HARDWARESPRITE | BIF_FLICKERFIXER; + b->RGBFormats = 1 | 2 | 512 | 1024 | 2048; + b->SoftSpriteFlags = 0; + b->BitsPerCannon = 8; + + for(i = 0; i < MAXMODES; i++) { + b->MaxHorValue[i] = 8192; + b->MaxVerValue[i] = 8192; + b->MaxHorResolution[i] = 8192; + b->MaxVerResolution[i] = 8192; + b->PixelClockCount[i] = 1; + } + + b->MemoryClock = CLOCK_HZ; + + //b->AllocCardMem = (void *)NULL; + //b->FreeCardMem = (void *)NULL; + b->SetSwitch = (void *)SetSwitch; + b->SetColorArray = (void *)SetColorArray; + b->SetDAC = (void *)SetDAC; + b->SetGC = (void *)SetGC; + b->SetPanning = (void *)SetPanning; + b->CalculateBytesPerRow = (void *)CalculateBytesPerRow; + b->CalculateMemory = (void *)CalculateMemory; + b->GetCompatibleFormats = (void *)GetCompatibleFormats; + b->SetDisplay = (void *)SetDisplay; + + b->ResolvePixelClock = (void *)ResolvePixelClock; + b->GetPixelClock = (void *)GetPixelClock; + b->SetClock = (void *)SetClock; + + b->SetMemoryMode = (void *)SetMemoryMode; + b->SetWriteMask = (void *)SetWriteMask; + b->SetClearMask = (void *)SetClearMask; + b->SetReadPlane = (void *)SetReadPlane; + + b->WaitVerticalSync = (void *)WaitVerticalSync; + //b->SetInterrupt = (void *)NULL; + + //b->WaitBlitter = (void *)NULL; + + //b->ScrollPlanar = (void *)NULL; + //b->UpdatePlanar = (void *)NULL; + + b->BlitPlanar2Chunky = (void *)BlitPlanar2Chunky; + b->BlitPlanar2Direct = (void *)BlitPlanar2Direct; + + b->FillRect = (void *)FillRect; + b->InvertRect = (void *)InvertRect; + b->BlitRect = (void *)BlitRect; + b->BlitTemplate = (void *)BlitTemplate; + b->BlitPattern = (void *)BlitPattern; + b->DrawLine = (void *)DrawLine; + b->BlitRectNoMaskComplete = (void *)BlitRectNoMaskComplete; + //b->EnableSoftSprite = (void *)NULL; + + //b->AllocCardMemAbs = (void *)NULL; + //b->SetSplitPosition = (void *)NULL; + //b->ReInitMemory = (void *)NULL; + //b->WriteYUVRect = (void *)NULL; + b->GetVSyncState = (void *)GetVSyncState; + //b->GetVBeamPos = (void *)NULL; + //b->SetDPMSLevel = (void *)NULL; + //b->ResetChip = (void *)NULL; + //b->GetFeatureAttrs = (void *)NULL; + //b->AllocBitMap = (void *)NULL; + //b->FreeBitMap = (void *)NULL; + //b->GetBitMapAttr = (void *)NULL; + + b->SetSprite = (void *)SetSprite; + b->SetSpritePosition = (void *)SetSpritePosition; + b->SetSpriteImage = (void *)SetSpriteImage; + b->SetSpriteColor = (void *)SetSpriteColor; + + //b->CreateFeature = (void *)NULL; + //b->SetFeatureAttrs = (void *)NULL; + //b->DeleteFeature = (void *)NULL; + + return 1; +} + +void SetDAC (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)) { + // Used to set the color format of the video card's RAMDAC. + // This needs no handling, since the PiStorm doesn't really have a RAMDAC or a video card chipset. +} + +void SetGC (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(BOOL border)) { + b->ModeInfo = mode_info; + // Send width, height and format to the RaspberryPi Targetable Graphics. + WRITESHORT(RTG_X1, mode_info->Width); + WRITESHORT(RTG_Y1, mode_info->Height); + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[b->RGBFormat]); + WRITESHORT(RTG_COMMAND, RTGCMD_SETGC); +} + +int setswitch = -1; +UWORD SetSwitch (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)) { + if (setswitch != enabled) { + setswitch = enabled; + } + + WRITEBYTE(RTG_U81, setswitch); + WRITESHORT(RTG_X1, setswitch); + WRITESHORT(RTG_COMMAND, RTGCMD_SETSWITCH); + + return 1 - enabled; +} + +void SetPanning (__REGA0(struct BoardInfo *b), __REGA1(UBYTE *addr), __REGD0(UWORD width), __REGD1(WORD x_offset), __REGD2(WORD y_offset), __REGD7(RGBFTYPE format)) { + // Set the panning offset, or the offset used for the current display area on the Pi. + // The address needs to have CARD_BASE subtracted from it to be used as an offset on the Pi side. +#ifndef IRTG + if (!b) + return; + + b->XOffset = x_offset; + b->YOffset = y_offset; + + WRITELONG(RTG_ADDR1, (unsigned long)addr); + WRITESHORT(RTG_X1, width); + WRITESHORT(RTG_X2, b->XOffset); + WRITESHORT(RTG_Y2, b->YOffset); + WRITESHORT(RTG_COMMAND, RTGCMD_SETPAN); +#else + IWRITECMD(RTGCMD_SETPAN); +#endif +} + +void SetColorArray (__REGA0(struct BoardInfo *b), __REGD0(UWORD start), __REGD1(UWORD num)) { + // Sets the color components of X color components for 8-bit paletted display modes. + if (!b->CLUT) + return; + + int j = start + num; + + for(int i = start; i < j; i++) { + //WRITEBYTE(RTG_U82, (unsigned char)b->CLUT[i].Red); + //WRITEBYTE(RTG_U83, (unsigned char)b->CLUT[i].Green); + //WRITEBYTE(RTG_U84, (unsigned char)b->CLUT[i].Blue); + unsigned long xrgb = 0 | (b->CLUT[i].Red << 16) | (b->CLUT[i].Green << 8) | (b->CLUT[i].Blue); + WRITEBYTE(RTG_U81, (unsigned char)i); + WRITELONG(RTG_RGB1, xrgb); + WRITESHORT(RTG_COMMAND, RTGCMD_SETCLUT); + } +} + +UWORD CalculateBytesPerRow (__REGA0(struct BoardInfo *b), __REGD0(UWORD width), __REGD7(RGBFTYPE format)) { + if (!b) + return 0; + + UWORD pitch = width; + + switch(format) { + default: + return pitch; + case 0x05: case 0x0A: case 0x0B: case 0x0D: + return (width * 2); + case 0x08: case 0x09: + return (width * 4); + } +} + +APTR CalculateMemory (__REGA0(struct BoardInfo *b), __REGA1(unsigned long addr), __REGD7(RGBFTYPE format)) { + /*if (!b) + return (APTR)addr; + + if (addr > (unsigned int)b->MemoryBase && addr < (((unsigned int)b->MemoryBase) + b->MemorySize)) { + addr = ((addr + 0x1000) & 0xFFFFF000); + }*/ + + return (APTR)addr; +} + +ULONG GetCompatibleFormats (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)) { + // It is of course compatible with all the formats ever. + return 0xFFFFFFFF; +} + +//static int display_enabled = 0; +UWORD SetDisplay (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)) { + // Enables or disables the display. + WRITEBYTE(RTG_U82, (unsigned char)enabled); + WRITESHORT(RTG_COMMAND, RTGCMD_SETDISPLAY); + + return 1; +} + +LONG ResolvePixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG pixel_clock), __REGD7(RGBFTYPE format)) { + mode_info->PixelClock = CLOCK_HZ; + mode_info->pll1.Clock = 0; + mode_info->pll2.ClockDivide = 1; + + return 0; +} + +ULONG GetPixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG index), __REGD7(RGBFTYPE format)) { + // Just return 100MHz. + return CLOCK_HZ; +} + +// None of these five really have to do anything. +void SetClock (__REGA0(struct BoardInfo *b)) { +} + +void SetMemoryMode (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)) { +} + +void SetWriteMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)) { +} + +void SetClearMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)) { +} + +void SetReadPlane (__REGA0(struct BoardInfo *b), __REGD0(UBYTE plane)) { +} + +static uint16_t vblank; + +void WaitVerticalSync (__REGA0(struct BoardInfo *b), __REGD0(BOOL toggle)) { + vblank = 0; + do { + READSHORT(RTG_WAITVSYNC, vblank); + } while (!vblank); +} + +BOOL GetVSyncState(__REGA0(struct BoardInfo *b), __REGD0(BOOL toggle)) { + READSHORT(RTG_INVBLANK, vblank); + return vblank; +} + +void FillRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(ULONG color), __REGD5(UBYTE mask), __REGD7(RGBFTYPE format)) { +#ifndef IRTG + if (!r) + return; + + WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); + + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); + WRITESHORT(RTG_X1, x); + WRITESHORT(RTG_X2, w); + WRITESHORT(RTG_Y1, y); + WRITESHORT(RTG_Y2, h); + WRITELONG(RTG_RGB1, color); + WRITESHORT(RTG_X3, r->BytesPerRow); + WRITEBYTE(RTG_U81, mask); + WRITESHORT(RTG_COMMAND, RTGCMD_FILLRECT); +#else + IWRITECMD(RTGCMD_FILLRECT); +#endif +} + +void InvertRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)) { +#ifndef IRTG + if (!r) + return; + + WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); + + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); + WRITESHORT(RTG_X1, x); + WRITESHORT(RTG_X2, w); + WRITESHORT(RTG_Y1, y); + WRITESHORT(RTG_Y2, h); + WRITESHORT(RTG_X3, r->BytesPerRow); + WRITEBYTE(RTG_U81, mask); + WRITESHORT(RTG_COMMAND, RTGCMD_INVERTRECT); +#else + IWRITECMD(RTGCMD_INVERTRECT); +#endif +} + +void BlitRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE mask), __REGD7(RGBFTYPE format)) { +#ifndef IRTG + if (!r) + return; + + WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); + + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); + WRITESHORT(RTG_X1, x); + WRITESHORT(RTG_X2, dx); + WRITESHORT(RTG_X3, w); + WRITESHORT(RTG_Y1, y); + WRITESHORT(RTG_Y2, dy); + WRITESHORT(RTG_Y3, h); + WRITESHORT(RTG_X4, r->BytesPerRow); + WRITEBYTE(RTG_U81, mask); + WRITESHORT(RTG_COMMAND, RTGCMD_BLITRECT); +#else + IWRITECMD(RTGCMD_BLITRECT); +#endif +} + +void BlitRectNoMaskComplete (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *rs), __REGA2(struct RenderInfo *rt), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE minterm), __REGD7(RGBFTYPE format)) { +#ifndef IRTG + if (!rs || !rt) + return; + + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); + WRITELONG(RTG_ADDR1, (unsigned long)rs->Memory); + WRITELONG(RTG_ADDR2, (unsigned long)rt->Memory); + WRITESHORT(RTG_X1, x); + WRITESHORT(RTG_X2, dx); + WRITESHORT(RTG_X3, w); + WRITESHORT(RTG_Y1, y); + WRITESHORT(RTG_Y2, dy); + WRITESHORT(RTG_Y3, h); + WRITESHORT(RTG_X4, rs->BytesPerRow); + WRITESHORT(RTG_X5, rt->BytesPerRow); + WRITEBYTE(RTG_U81, minterm); + WRITESHORT(RTG_COMMAND, RTGCMD_BLITRECT_NOMASK_COMPLETE); +#else + IWRITECMD(RTGCMD_BLITRECT_NOMASK_COMPLETE); +#endif +} + +void BlitTemplate (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Template *t), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)) { +#ifndef IRTG + if (!r || !t) return; + if (w < 1 || h < 1) return; + + WRITELONG(RTG_ADDR2, (unsigned long)r->Memory); + + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); + WRITESHORT(RTG_X1, x); + WRITESHORT(RTG_X2, w); + WRITESHORT(RTG_X3, t->XOffset); + WRITESHORT(RTG_Y1, y); + WRITESHORT(RTG_Y2, h); + WRITESHORT(RTG_Y3, 0); + + if ((unsigned long)t->Memory > CHIP_RAM_SIZE) { + WRITELONG(RTG_ADDR1, (unsigned long)t->Memory); + } + else { + unsigned long dest = CARD_SCRATCH; + memcpy((unsigned char *)dest, t->Memory, (t->BytesPerRow * h)); + WRITELONG(RTG_ADDR1, (unsigned long)dest); + WRITELONG(RTG_ADDR3, (unsigned long)t->Memory); + } + + WRITELONG(RTG_RGB1, t->FgPen); + WRITELONG(RTG_RGB2, t->BgPen); + + WRITESHORT(RTG_X4, r->BytesPerRow); + WRITESHORT(RTG_X5, t->BytesPerRow); + + WRITEBYTE(RTG_U81, mask); + WRITEBYTE(RTG_U82, t->DrawMode); + WRITESHORT(RTG_COMMAND, RTGCMD_BLITTEMPLATE); +#else + IWRITECMD(RTGCMD_BLITTEMPLATE); +#endif +} + +void BlitPattern (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Pattern *p), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)) { +#ifndef IRTG + if (!r || !p) return; + if (w < 1 || h < 1) return; + + WRITELONG(RTG_ADDR2, (unsigned long)r->Memory); + + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); + WRITESHORT(RTG_X1, x); + WRITESHORT(RTG_X2, w); + WRITESHORT(RTG_X3, p->XOffset); + WRITESHORT(RTG_Y1, y); + WRITESHORT(RTG_Y2, h); + WRITESHORT(RTG_Y3, p->YOffset); + + if ((unsigned long)p->Memory > CHIP_RAM_SIZE) { + WRITELONG(RTG_ADDR1, (unsigned long)p->Memory); + } + else { + unsigned long dest = CARD_SCRATCH; + memcpy((unsigned char *)dest, p->Memory, (2 * (1 << p->Size))); + WRITELONG(RTG_ADDR1, (unsigned long)dest); + } + + WRITELONG(RTG_RGB1, p->FgPen); + WRITELONG(RTG_RGB2, p->BgPen); + + WRITESHORT(RTG_X4, r->BytesPerRow); + WRITESHORT(RTG_X5, (1 << p->Size)); + + WRITEBYTE(RTG_U81, mask); + WRITEBYTE(RTG_U82, p->DrawMode); + WRITEBYTE(RTG_U83, (1 << p->Size)); + WRITESHORT(RTG_COMMAND, RTGCMD_BLITPATTERN); +#else + IWRITECMD(RTGCMD_BLITPATTERN); +#endif +} + +void DrawLine (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Line *l), __REGD0(UBYTE mask), __REGD7(RGBFTYPE format)) { +#ifndef IRTG + if (!r || !b) return; + + WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); + + WRITELONG(RTG_RGB1, l->FgPen); + WRITELONG(RTG_RGB2, l->BgPen); + + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); + + WRITESHORT(RTG_X1, l->X); + WRITESHORT(RTG_X2, l->dX); + WRITESHORT(RTG_Y1, l->Y); + WRITESHORT(RTG_Y2, l->dY); + + WRITESHORT(RTG_X3, l->Length); + WRITESHORT(RTG_Y3, l->LinePtrn); + + WRITESHORT(RTG_X4, r->BytesPerRow); + WRITESHORT(RTG_X5, l->PatternShift); + + WRITEBYTE(RTG_U81, mask); + WRITEBYTE(RTG_U82, l->DrawMode); + WRITEBYTE(RTG_U83, l->pad); + WRITESHORT(RTG_COMMAND, RTGCMD_DRAWLINE); +#else + IWRITECMD(RTGCMD_DRAWLINE); +#endif +} + +void BlitPlanar2Chunky (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bm), __REGA2(struct RenderInfo *r), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)) { +// iRTG path disabled for now, since it's really slow, see note in rtg-gfx.c. +//#ifndef IRTG + if (!b || !r) + return; + + //uint32_t plane_size = bm->BytesPerRow * bm->Rows; + + uint32_t template_addr = CARD_SCRATCH; + + uint16_t plane_mask = mask; + uint8_t ff_mask = 0x00; + uint8_t cur_plane = 0x01; + + uint16_t line_size = (w >> 3) + 2; + uint32_t output_plane_size = line_size * h; + //uint16_t x_offset = (x >> 3); + + WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); + WRITELONG(RTG_ADDR2, template_addr); + WRITESHORT(RTG_X4, r->BytesPerRow); + WRITESHORT(RTG_X5, line_size); + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[r->RGBFormat]); + + WRITEBYTE(RTG_U81, mask); + WRITEBYTE(RTG_U82, minterm); + + for (int16_t i = 0; i < bm->Depth; i++) { + uint16_t x_offset = (x >> 3); + if ((uint32_t)bm->Planes[i] == 0xFFFFFFFF) { + uint8_t* dest = (uint8_t*)((uint32_t)template_addr); + memset(dest, 0xFF, output_plane_size); + } + else if (bm->Planes[i] != NULL) { + uint8_t* bmp_mem = (uint8_t*)bm->Planes[i] + (y * bm->BytesPerRow) + x_offset; + uint8_t* dest = (uint8_t*)((uint32_t)template_addr); + for (int16_t y_line = 0; y_line < h; y_line++) { + memcpy(dest, bmp_mem, line_size); + dest += line_size; + bmp_mem += bm->BytesPerRow; + } + } + else { + plane_mask &= (cur_plane ^ 0xFF); + } + cur_plane <<= 1; + template_addr += output_plane_size; + } + + WRITESHORT(RTG_X1, (x & 0x07)); + WRITESHORT(RTG_X2, dx); + WRITESHORT(RTG_X3, w); + WRITESHORT(RTG_Y1, 0); + WRITESHORT(RTG_Y2, dy); + WRITESHORT(RTG_Y3, h); + + WRITESHORT(RTG_U1, (plane_mask << 8 | ff_mask)); + WRITEBYTE(RTG_U83, bm->Depth); + + WRITESHORT(RTG_COMMAND, RTGCMD_P2C); +//#else +// IWRITECMD(RTGCMD_P2C); +//#endif +} + +void BlitPlanar2Direct (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bm), __REGA2(struct RenderInfo *r), __REGA3(struct ColorIndexMapping *clut), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)) { + if (!b || !r) + return; + + //uint32_t plane_size = bm->BytesPerRow * bm->Rows; + + uint32_t template_addr = CARD_SCRATCH; + + uint16_t plane_mask = mask; + uint8_t ff_mask = 0x00; + uint8_t cur_plane = 0x01; + + uint16_t line_size = (w >> 3) + 2; + uint32_t output_plane_size = line_size * h; + //uint16_t x_offset = (x >> 3); + + WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); + WRITELONG(RTG_ADDR2, template_addr); + WRITESHORT(RTG_X4, r->BytesPerRow); + WRITESHORT(RTG_X5, line_size); + WRITESHORT(RTG_FORMAT, rgbf_to_rtg[r->RGBFormat]); + + WRITEBYTE(RTG_U81, mask); + WRITEBYTE(RTG_U82, minterm); + + memcpy((uint8_t*)((uint32_t)template_addr), clut->Colors, (256 << 2)); + template_addr += (256 << 2); + + for (int16_t i = 0; i < bm->Depth; i++) { + uint16_t x_offset = (x >> 3); + if ((uint32_t)bm->Planes[i] == 0xFFFFFFFF) { + uint8_t* dest = (uint8_t*)((uint32_t)template_addr); + memset(dest, 0xFF, output_plane_size); + } + else if (bm->Planes[i] != NULL) { + uint8_t* bmp_mem = (uint8_t*)bm->Planes[i] + (y * bm->BytesPerRow) + x_offset; + uint8_t* dest = (uint8_t*)((uint32_t)template_addr); + for (int16_t y_line = 0; y_line < h; y_line++) { + memcpy(dest, bmp_mem, line_size); + dest += line_size; + bmp_mem += bm->BytesPerRow; + } + } + else { + plane_mask &= (cur_plane ^ 0xFF); + } + cur_plane <<= 1; + template_addr += output_plane_size; + } + + WRITESHORT(RTG_X1, (x & 0x07)); + WRITESHORT(RTG_X2, dx); + WRITESHORT(RTG_X3, w); + WRITESHORT(RTG_Y1, 0); + WRITESHORT(RTG_Y2, dy); + WRITESHORT(RTG_Y3, h); + + WRITESHORT(RTG_U1, (plane_mask << 8 | ff_mask)); + WRITEBYTE(RTG_U83, bm->Depth); + + WRITESHORT(RTG_COMMAND, RTGCMD_P2D); +} + +void SetSprite (__REGA0(struct BoardInfo *b), __REGD0(BOOL what), __REGD7(RGBFTYPE format)) { + WRITESHORT(RTG_COMMAND, RTGCMD_SETSPRITE); +} + +void SetSpritePosition (__REGA0(struct BoardInfo *b), __REGD0(WORD x), __REGD1(WORD y), __REGD7(RGBFTYPE format)) { + WRITESHORT(RTG_X1, x); + WRITESHORT(RTG_Y1, y); + + WRITESHORT(RTG_COMMAND, RTGCMD_SETSPRITEPOS); +} + +void SetSpriteImage (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)) { + WRITESHORT(RTG_X1, b->XOffset); + WRITESHORT(RTG_Y1, b->YOffset); + WRITEBYTE(RTG_U81, b->MouseWidth); + WRITEBYTE(RTG_U82, b->MouseHeight); + + uint8_t* dest = (uint8_t*)((uint32_t)CARD_SCRATCH); + uint8_t* src = (uint8_t *)b->MouseImage; + uint16_t data_size = ((b->MouseWidth >> 3) * 2) * (b->MouseHeight); + + if (b->MouseWidth > 16) src += 8; + else src += 4; + + memcpy(dest, src, data_size); + + WRITELONG(RTG_ADDR2, CARD_SCRATCH); + + WRITESHORT(RTG_COMMAND, RTGCMD_SETSPRITEIMAGE); +} + +void SetSpriteColor (__REGA0(struct BoardInfo *b), __REGD0(UBYTE idx), __REGD1(UBYTE R), __REGD2(UBYTE G), __REGD3(UBYTE B), __REGD7(RGBFTYPE format)) { + WRITEBYTE(RTG_U81, R); + WRITEBYTE(RTG_U82, G); + WRITEBYTE(RTG_U83, B); + WRITEBYTE(RTG_U84, idx); + + WRITESHORT(RTG_COMMAND, RTGCMD_SETSPRITECOLOR); +} + +static uint32_t device_vectors[] = { + (uint32_t)OpenLib, + (uint32_t)CloseLib, + (uint32_t)ExpungeLib, + 0, + (uint32_t)FindCard, + (uint32_t)InitCard, + -1 +}; + +struct InitTable +{ + ULONG LibBaseSize; + APTR FunctionTable; + APTR DataTable; + APTR InitLibTable; +}; + +const uint32_t auto_init_tables[4] = { + sizeof(struct Library), + (uint32_t)device_vectors, + 0, + (uint32_t)InitLib, +}; diff --git a/platforms/amiga/rtg/rtg_driver_amiga/pigfx.c b/platforms/amiga/rtg/rtg_driver_amiga/pigfx.c deleted file mode 100644 index 37c75b0..0000000 --- a/platforms/amiga/rtg/rtg_driver_amiga/pigfx.c +++ /dev/null @@ -1,776 +0,0 @@ -// SPDX-License-Identifier: MIT - -// PiStorm RTG driver, VBCC edition. -// Based in part on the ZZ9000 RTG driver. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "boardinfo.h" -#include "rtg_enums.h" - -#define WRITESHORT(cmd, val) *(unsigned short *)((unsigned long)(b->RegisterBase)+cmd) = val; -#define WRITELONG(cmd, val) *(unsigned long *)((unsigned long)(b->RegisterBase)+cmd) = val; -#define WRITEBYTE(cmd, val) *(unsigned char *)((unsigned long)(b->RegisterBase)+cmd) = val; - -#define CHECKRTG *((unsigned short *)(CARD_OFFSET)) - -#define CARD_OFFSET 0x70000000 -#define CARD_REGSIZE 0x00010000 -#define CARD_MEMSIZE 0x02000000 // 32MB "VRAM" -#define CARD_SCRATCH 0x72010000 - -#define CHIP_RAM_SIZE 0x00200000 // Chip RAM offset, 2MB - -const unsigned short rgbf_to_rtg[16] = { - RTGFMT_8BIT, // 0x00 - RTGFMT_8BIT, // 0x01 - 0, // 0x02 - 0, // 0x03 - 0, // 0x04 - RTGFMT_RGB555, // 0x05 - 0, // 0x06 - 0, // 0x07 - RTGFMT_RGB32, // 0x08 - RTGFMT_RGB32, // 0x09 - RTGFMT_RBG565, // 0x0A - RTGFMT_RGB555, // 0x0B - 0, // 0x0C - RTGFMT_RGB555, // 0x0D - 0, // 0x0E - 0, // 0x0F -}; - -struct GFXBase { - struct Library libNode; - BPTR segList; - struct ExecBase* sysBase; - struct ExpansionBase* expansionBase; -}; - -int FindCard(__REGA0(struct BoardInfo* b)); -int InitCard(__REGA0(struct BoardInfo* b)); - -void SetDAC (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)); -void SetGC (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(BOOL border)); -void SetColorArray (__REGA0(struct BoardInfo *b), __REGD0(UWORD start), __REGD1(UWORD num)); -void SetPanning (__REGA0(struct BoardInfo *b), __REGA1(UBYTE *addr), __REGD0(UWORD width), __REGD1(WORD x_offset), __REGD2(WORD y_offset), __REGD7(RGBFTYPE format)); -UWORD SetSwitch (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)); -UWORD SetDisplay (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)); - -UWORD CalculateBytesPerRow (__REGA0(struct BoardInfo *b), __REGD0(UWORD width), __REGD7(RGBFTYPE format)); -APTR CalculateMemory (__REGA0(struct BoardInfo *b), __REGA1(unsigned long addr), __REGD7(RGBFTYPE format)); -ULONG GetCompatibleFormats (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)); - -LONG ResolvePixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG pixel_clock), __REGD7(RGBFTYPE format)); -ULONG GetPixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG index), __REGD7(RGBFTYPE format)); -void SetClock (__REGA0(struct BoardInfo *b)); - -void SetMemoryMode (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)); -void SetWriteMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)); -void SetClearMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)); -void SetReadPlane (__REGA0(struct BoardInfo *b), __REGD0(UBYTE plane)); - -void WaitVerticalSync (__REGA0(struct BoardInfo *b), __REGD0(BOOL toggle)); - -void FillRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(ULONG color), __REGD5(UBYTE mask), __REGD7(RGBFTYPE format)); -void InvertRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)); -void BlitRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE mask), __REGD7(RGBFTYPE format)); -void BlitRectNoMaskComplete (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *rs), __REGA2(struct RenderInfo *rt), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE minterm), __REGD7(RGBFTYPE format)); -void BlitTemplate (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Template *t), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)); -void BlitPattern (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Pattern *p), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)); -void DrawLine (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Line *l), __REGD0(UBYTE mask), __REGD7(RGBFTYPE format)); - -void BlitPlanar2Chunky (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bm), __REGA2(struct RenderInfo *r), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)); -void BlitPlanar2Direct (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bmp), __REGA2(struct RenderInfo *r), __REGA3(struct ColorIndexMapping *clut), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)); - -static ULONG LibStart(void) { - return(-1); -} - -static const char LibraryName[] = "PiRTG.card"; -static const char LibraryID[] = "$VER: PiRTG.card 0.01\r\n"; - -__saveds struct GFXBase* OpenLib(__REGA6(struct GFXBase *gfxbase)); -BPTR __saveds CloseLib(__REGA6(struct GFXBase *gfxbase)); -BPTR __saveds ExpungeLib(__REGA6(struct GFXBase *exb)); -ULONG ExtFuncLib(void); -__saveds struct GFXBase* InitLib(__REGA6(struct ExecBase *sysbase), - __REGA0(BPTR seglist), - __REGD0(struct GFXBase *exb)); - -static const APTR FuncTab[] = { - (APTR)OpenLib, - (APTR)CloseLib, - (APTR)ExpungeLib, - (APTR)ExtFuncLib, - - (APTR)FindCard, - (APTR)InitCard, - (APTR)((LONG)-1) -}; - -struct InitTable -{ - ULONG LibBaseSize; - APTR FunctionTable; - APTR DataTable; - APTR InitLibTable; -}; - -static struct InitTable InitTab = { - (ULONG) sizeof(struct GFXBase), - (APTR) FuncTab, - (APTR) NULL, - (APTR) InitLib -}; - -static const struct Resident ROMTag = { - RTC_MATCHWORD, - &ROMTag, - &ROMTag + 1, - RTF_AUTOINIT, - 83, - NT_LIBRARY, - 0, - (char *)LibraryName, - (char *)LibraryID, - (APTR)&InitTab -}; - -#define CLOCK_HZ 100000000 - -static struct GFXBase *_gfxbase; -const char *gfxname = "PiStorm RTG"; -char dummies[128]; - -__saveds struct GFXBase* InitLib(__REGA6(struct ExecBase *sysbase), - __REGA0(BPTR seglist), - __REGD0(struct GFXBase *exb)) -{ - _gfxbase = exb; - return _gfxbase; -} - -__saveds struct GFXBase* OpenLib(__REGA6(struct GFXBase *gfxbase)) -{ - gfxbase->libNode.lib_OpenCnt++; - gfxbase->libNode.lib_Flags &= ~LIBF_DELEXP; - - return gfxbase; -} - -BPTR __saveds CloseLib(__REGA6(struct GFXBase *gfxbase)) -{ - gfxbase->libNode.lib_OpenCnt--; - - if (!gfxbase->libNode.lib_OpenCnt) { - if (gfxbase->libNode.lib_Flags & LIBF_DELEXP) { - return (ExpungeLib(gfxbase)); - } - } - return 0; -} - -BPTR __saveds ExpungeLib(__REGA6(struct GFXBase *exb)) -{ - BPTR seglist; - struct ExecBase *SysBase = *(struct ExecBase **)4L; - - if(!exb->libNode.lib_OpenCnt) { - ULONG negsize, possize, fullsize; - UBYTE *negptr = (UBYTE *)exb; - - seglist = exb->segList; - - Remove((struct Node *)exb); - - negsize = exb->libNode.lib_NegSize; - possize = exb->libNode.lib_PosSize; - fullsize = negsize + possize; - negptr -= negsize; - - FreeMem(negptr, fullsize); - return(seglist); - } - - exb->libNode.lib_Flags |= LIBF_DELEXP; - return 0; -} - -ULONG ExtFuncLib(void) -{ - return 0; -} - -static LONG zorro_version = 0; - -static struct GFXData *gfxdata; -//MNTZZ9KRegs* registers; - -#define LOADLIB(a, b) if ((a = (struct a*)OpenLibrary(b,0L))==NULL) { \ - KPrintF("Failed to load %s.\n", b); \ - return 0; \ - } \ - -static BYTE card_already_found; -static BYTE card_initialized; - -int FindCard(__REGA0(struct BoardInfo* b)) { - //if (card_already_found) -// return 1; - uint16_t card_check = CHECKRTG; - if (card_check != 0xFFCF) { - // RTG not enabled - return 0; - } - - struct ConfigDev* cd = NULL; - struct ExpansionBase *ExpansionBase = NULL; - struct DOSBase *DOSBase = NULL; - struct IntuitionBase *IntuitionBase = NULL; - struct ExecBase *SysBase = *(struct ExecBase **)4L; - - LOADLIB(ExpansionBase, "expansion.library"); - LOADLIB(DOSBase, "dos.library"); - LOADLIB(IntuitionBase, "intuition.library"); - - b->MemorySize = CARD_MEMSIZE; - b->RegisterBase = (void *)CARD_OFFSET; - b->MemoryBase = (void *)(CARD_OFFSET + CARD_REGSIZE); - - return 1; -} - -#define HWSPRITE 1 -#define VGASPLIT (1 << 6) -#define FLICKERFIXER (1 << 12) -#define INDISPLAYCHAIN (1 << 20) -#define DIRECTACCESS (1 << 26) - -int InitCard(__REGA0(struct BoardInfo* b)) { - //if (!card_initialized) -// card_initialized = 1; -// else - //return 1; - - int max, i; - struct ExecBase *SysBase = *(struct ExecBase **)4L; - - b->CardBase = (struct CardBase *)_gfxbase; - b->ExecBase = SysBase; - b->BoardName = "PiStorm RTG"; - b->BoardType = BT_MNT_ZZ9000; - b->PaletteChipType = PCT_MNT_ZZ9000; - b->GraphicsControllerType = GCT_MNT_ZZ9000; - - b->Flags = BIF_INDISPLAYCHAIN | BIF_GRANTDIRECTACCESS; - b->RGBFormats = 1 | 2 | 512 | 1024 | 2048; - b->SoftSpriteFlags = 0; - b->BitsPerCannon = 8; - - for(i = 0; i < MAXMODES; i++) { - b->MaxHorValue[i] = 1920; - b->MaxVerValue[i] = 1080; - b->MaxHorResolution[i] = 1920; - b->MaxVerResolution[i] = 1080; - b->PixelClockCount[i] = 1; - } - - b->MemoryClock = CLOCK_HZ; - - //b->AllocCardMem = (void *)NULL; - //b->FreeCardMem = (void *)NULL; - b->SetSwitch = (void *)SetSwitch; - b->SetColorArray = (void *)SetColorArray; - b->SetDAC = (void *)SetDAC; - b->SetGC = (void *)SetGC; - b->SetPanning = (void *)SetPanning; - b->CalculateBytesPerRow = (void *)CalculateBytesPerRow; - b->CalculateMemory = (void *)CalculateMemory; - b->GetCompatibleFormats = (void *)GetCompatibleFormats; - b->SetDisplay = (void *)SetDisplay; - - b->ResolvePixelClock = (void *)ResolvePixelClock; - b->GetPixelClock = (void *)GetPixelClock; - b->SetClock = (void *)SetClock; - - b->SetMemoryMode = (void *)SetMemoryMode; - b->SetWriteMask = (void *)SetWriteMask; - b->SetClearMask = (void *)SetClearMask; - b->SetReadPlane = (void *)SetReadPlane; - - b->WaitVerticalSync = (void *)WaitVerticalSync; - //b->SetInterrupt = (void *)NULL; - - //b->WaitBlitter = (void *)NULL; - - //b->ScrollPlanar = (void *)NULL; - //b->UpdatePlanar = (void *)NULL; - - //b->BlitPlanar2Chunky = (void *)BlitPlanar2Chunky; - //b->BlitPlanar2Direct = (void *)NULL; - - b->FillRect = (void *)FillRect; - b->InvertRect = (void *)InvertRect; - b->BlitRect = (void *)BlitRect; - b->BlitTemplate = (void *)BlitTemplate; - b->BlitPattern = (void *)BlitPattern; - b->DrawLine = (void *)DrawLine; - b->BlitRectNoMaskComplete = (void *)BlitRectNoMaskComplete; - //b->EnableSoftSprite = (void *)NULL; - - //b->AllocCardMemAbs = (void *)NULL; - //b->SetSplitPosition = (void *)NULL; - //b->ReInitMemory = (void *)NULL; - //b->WriteYUVRect = (void *)NULL; - //b->GetVSyncState = (void *)NULL; - //b->GetVBeamPos = (void *)NULL; - //b->SetDPMSLevel = (void *)NULL; - //b->ResetChip = (void *)NULL; - //b->GetFeatureAttrs = (void *)NULL; - //b->AllocBitMap = (void *)NULL; - //b->FreeBitMap = (void *)NULL; - //b->GetBitMapAttr = (void *)NULL; - - //b->SetSprite = (void *)NULL; - //b->SetSpritePosition = (void *)NULL; - //b->SetSpriteImage = (void *)NULL; - //b->SetSpriteColor = (void *)NULL; - - //b->CreateFeature = (void *)NULL; - //b->SetFeatureAttrs = (void *)NULL; - //b->DeleteFeature = (void *)NULL; - - return 1; -} - -void SetDAC (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)) { - // Used to set the color format of the video card's RAMDAC. - // This needs no handling, since the PiStorm doesn't really have a RAMDAC or a video card chipset. -} - -void SetGC (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(BOOL border)) { - b->ModeInfo = mode_info; - // Send width, height and format to the RaspberryPi Targetable Graphics. - WRITESHORT(RTG_X1, mode_info->Width); - WRITESHORT(RTG_Y1, mode_info->Height); - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[b->RGBFormat]); - WRITESHORT(RTG_COMMAND, RTGCMD_SETGC); -} - -int setswitch = -1; -UWORD SetSwitch (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)) { - if (setswitch != enabled) { - setswitch = enabled; - } - - WRITEBYTE(RTG_U81, setswitch); - WRITESHORT(RTG_X1, setswitch); - WRITESHORT(RTG_COMMAND, RTGCMD_SETSWITCH); - - return 1 - enabled; -} - -void SetPanning (__REGA0(struct BoardInfo *b), __REGA1(UBYTE *addr), __REGD0(UWORD width), __REGD1(WORD x_offset), __REGD2(WORD y_offset), __REGD7(RGBFTYPE format)) { - // Set the panning offset, or the offset used for the current display area on the Pi. - // The address needs to have CARD_BASE subtracted from it to be used as an offset on the Pi side. - if (!b) - return; - - b->XOffset = x_offset; - b->YOffset = y_offset; - - WRITELONG(RTG_ADDR1, (unsigned long)addr); - WRITESHORT(RTG_X1, width); - WRITESHORT(RTG_X2, b->XOffset); - WRITESHORT(RTG_Y2, b->YOffset); - WRITESHORT(RTG_COMMAND, RTGCMD_SETPAN); -} - -void SetColorArray (__REGA0(struct BoardInfo *b), __REGD0(UWORD start), __REGD1(UWORD num)) { - // Sets the color components of X color components for 8-bit paletted display modes. - if (!b->CLUT) - return; - - int j = start + num; - - for(int i = start; i < j; i++) { - //WRITEBYTE(RTG_U82, (unsigned char)b->CLUT[i].Red); - //WRITEBYTE(RTG_U83, (unsigned char)b->CLUT[i].Green); - //WRITEBYTE(RTG_U84, (unsigned char)b->CLUT[i].Blue); - unsigned long xrgb = 0 | (b->CLUT[i].Red << 16) | (b->CLUT[i].Green << 8) | (b->CLUT[i].Blue); - WRITEBYTE(RTG_U81, (unsigned char)i); - WRITELONG(RTG_RGB1, xrgb); - WRITESHORT(RTG_COMMAND, RTGCMD_SETCLUT); - } -} - -UWORD CalculateBytesPerRow (__REGA0(struct BoardInfo *b), __REGD0(UWORD width), __REGD7(RGBFTYPE format)) { - if (!b) - return 0; - - UWORD pitch = width; - - switch(format) { - default: - return pitch; - case 0x05: case 0x0A: case 0x0B: case 0x0D: - return (width * 2); - case 0x08: case 0x09: - return (width * 4); - } -} - -APTR CalculateMemory (__REGA0(struct BoardInfo *b), __REGA1(unsigned long addr), __REGD7(RGBFTYPE format)) { - /*if (!b) - return (APTR)addr; - - if (addr > (unsigned int)b->MemoryBase && addr < (((unsigned int)b->MemoryBase) + b->MemorySize)) { - addr = ((addr + 0x1000) & 0xFFFFF000); - }*/ - - return (APTR)addr; -} - -ULONG GetCompatibleFormats (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)) { - // It is of course compatible with all the formats ever. - return 0xFFFFFFFF; -} - -static int display_enabled = 0; -UWORD SetDisplay (__REGA0(struct BoardInfo *b), __REGD0(UWORD enabled)) { - // Enables or disables the display. - WRITEBYTE(RTG_U82, (unsigned char)enabled); - WRITESHORT(RTG_COMMAND, RTGCMD_SETDISPLAY); - - return 1; -} - -LONG ResolvePixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG pixel_clock), __REGD7(RGBFTYPE format)) { - mode_info->PixelClock = CLOCK_HZ; - mode_info->pll1.Clock = 0; - mode_info->pll2.ClockDivide = 1; - - return 0; -} - -ULONG GetPixelClock (__REGA0(struct BoardInfo *b), __REGA1(struct ModeInfo *mode_info), __REGD0(ULONG index), __REGD7(RGBFTYPE format)) { - // Just return 100MHz. - return CLOCK_HZ; -} - -// None of these five really have to do anything. -void SetClock (__REGA0(struct BoardInfo *b)) { -} - -void SetMemoryMode (__REGA0(struct BoardInfo *b), __REGD7(RGBFTYPE format)) { -} - -void SetWriteMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)) { -} - -void SetClearMask (__REGA0(struct BoardInfo *b), __REGD0(UBYTE mask)) { -} - -void SetReadPlane (__REGA0(struct BoardInfo *b), __REGD0(UBYTE plane)) { -} - -void WaitVerticalSync (__REGA0(struct BoardInfo *b), __REGD0(BOOL toggle)) { - // I don't know why this one has a bool in D0, but it isn't used for anything. -} - -void FillRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(ULONG color), __REGD5(UBYTE mask), __REGD7(RGBFTYPE format)) { - if (!r) - return; - - WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); - - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); - WRITESHORT(RTG_X1, x); - WRITESHORT(RTG_X2, w); - WRITESHORT(RTG_Y1, y); - WRITESHORT(RTG_Y2, h); - WRITELONG(RTG_RGB1, color); - WRITESHORT(RTG_X3, r->BytesPerRow); - WRITEBYTE(RTG_U81, mask); - WRITESHORT(RTG_COMMAND, RTGCMD_FILLRECT); -} - -void InvertRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)) { - if (!r) - return; - WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); - - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); - WRITESHORT(RTG_X1, x); - WRITESHORT(RTG_X2, w); - WRITESHORT(RTG_Y1, y); - WRITESHORT(RTG_Y2, h); - WRITESHORT(RTG_X3, r->BytesPerRow); - WRITEBYTE(RTG_U81, mask); - WRITESHORT(RTG_COMMAND, RTGCMD_INVERTRECT); -} - -void BlitRect (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE mask), __REGD7(RGBFTYPE format)) { - if (!r) - return; - - WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); - - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); - WRITESHORT(RTG_X1, x); - WRITESHORT(RTG_X2, dx); - WRITESHORT(RTG_X3, w); - WRITESHORT(RTG_Y1, y); - WRITESHORT(RTG_Y2, dy); - WRITESHORT(RTG_Y3, h); - WRITESHORT(RTG_X4, r->BytesPerRow); - WRITEBYTE(RTG_U81, mask); - WRITESHORT(RTG_COMMAND, RTGCMD_BLITRECT); -} - -void BlitRectNoMaskComplete (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *rs), __REGA2(struct RenderInfo *rt), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD dx), __REGD3(WORD dy), __REGD4(WORD w), __REGD5(WORD h), __REGD6(UBYTE minterm), __REGD7(RGBFTYPE format)) { - if (!rs || !rt) - return; - - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); - WRITELONG(RTG_ADDR1, (unsigned long)rs->Memory); - WRITELONG(RTG_ADDR2, (unsigned long)rt->Memory); - WRITESHORT(RTG_X1, x); - WRITESHORT(RTG_X2, dx); - WRITESHORT(RTG_X3, w); - WRITESHORT(RTG_Y1, y); - WRITESHORT(RTG_Y2, dy); - WRITESHORT(RTG_Y3, h); - WRITESHORT(RTG_X4, rs->BytesPerRow); - WRITESHORT(RTG_X5, rt->BytesPerRow); - WRITEBYTE(RTG_U81, minterm); - WRITESHORT(RTG_COMMAND, RTGCMD_BLITRECT_NOMASK_COMPLETE); -} - -void BlitTemplate (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Template *t), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)) { - if (!r || !t) return; - if (w < 1 || h < 1) return; - - WRITELONG(RTG_ADDR2, (unsigned long)r->Memory); - - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); - WRITESHORT(RTG_X1, x); - WRITESHORT(RTG_X2, w); - WRITESHORT(RTG_X3, t->XOffset); - WRITESHORT(RTG_Y1, y); - WRITESHORT(RTG_Y2, h); - WRITESHORT(RTG_Y3, 0); - - if ((unsigned long)t->Memory > CHIP_RAM_SIZE) { - WRITELONG(RTG_ADDR1, (unsigned long)t->Memory); - } - else { - unsigned long dest = CARD_SCRATCH; - memcpy((unsigned char *)dest, t->Memory, (t->BytesPerRow * h)); - WRITELONG(RTG_ADDR1, (unsigned long)dest); - WRITELONG(RTG_ADDR3, (unsigned long)t->Memory); - } - - WRITELONG(RTG_RGB1, t->FgPen); - WRITELONG(RTG_RGB2, t->BgPen); - - WRITESHORT(RTG_X4, r->BytesPerRow); - WRITESHORT(RTG_X5, t->BytesPerRow); - - WRITEBYTE(RTG_U81, mask); - WRITEBYTE(RTG_U82, t->DrawMode); - WRITESHORT(RTG_COMMAND, RTGCMD_BLITTEMPLATE); -} - -void BlitPattern (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Pattern *p), __REGD0(WORD x), __REGD1(WORD y), __REGD2(WORD w), __REGD3(WORD h), __REGD4(UBYTE mask), __REGD7(RGBFTYPE format)) { - if (!r || !p) return; - if (w < 1 || h < 1) return; - - WRITELONG(RTG_ADDR2, (unsigned long)r->Memory); - - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); - WRITESHORT(RTG_X1, x); - WRITESHORT(RTG_X2, w); - WRITESHORT(RTG_X3, p->XOffset); - WRITESHORT(RTG_Y1, y); - WRITESHORT(RTG_Y2, h); - WRITESHORT(RTG_Y3, p->YOffset); - - if ((unsigned long)p->Memory > CHIP_RAM_SIZE) { - WRITELONG(RTG_ADDR1, (unsigned long)p->Memory); - } - else { - unsigned long dest = CARD_SCRATCH; - memcpy((unsigned char *)dest, p->Memory, (2 * (1 << p->Size))); - WRITELONG(RTG_ADDR1, (unsigned long)dest); - } - - WRITELONG(RTG_RGB1, p->FgPen); - WRITELONG(RTG_RGB2, p->BgPen); - - WRITESHORT(RTG_X4, r->BytesPerRow); - WRITESHORT(RTG_X5, (1 << p->Size)); - - WRITEBYTE(RTG_U81, mask); - WRITEBYTE(RTG_U82, p->DrawMode); - WRITEBYTE(RTG_U83, (1 << p->Size)); - WRITESHORT(RTG_COMMAND, RTGCMD_BLITPATTERN); -} - -void DrawLine (__REGA0(struct BoardInfo *b), __REGA1(struct RenderInfo *r), __REGA2(struct Line *l), __REGD0(UBYTE mask), __REGD7(RGBFTYPE format)) { - if (!r || !b) return; - - WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); - - WRITELONG(RTG_RGB1, l->FgPen); - WRITELONG(RTG_RGB2, l->BgPen); - - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[format]); - - WRITESHORT(RTG_X1, l->X); - WRITESHORT(RTG_X2, l->dX); - WRITESHORT(RTG_Y1, l->Y); - WRITESHORT(RTG_Y2, l->dY); - - WRITESHORT(RTG_X3, l->Length); - WRITESHORT(RTG_Y3, l->LinePtrn); - - WRITESHORT(RTG_X4, r->BytesPerRow); - WRITESHORT(RTG_X5, l->PatternShift); - - WRITEBYTE(RTG_U81, mask); - WRITEBYTE(RTG_U82, l->DrawMode); - WRITEBYTE(RTG_U83, l->pad); - - WRITESHORT(RTG_COMMAND, RTGCMD_DRAWLINE); -} - -void BlitPlanar2Chunky_Broken (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bm), __REGA2(struct RenderInfo *r), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)) { - if (!r || !b || !bm) return; - - //unsigned long bmp_size = bm->BytesPerRow * bm->Rows; - unsigned char planemask_0 = 0, planemask = 0; - //unsigned short line_size = (w >> 3) + 2; - //unsigned long output_plane_size = line_size * h; - unsigned long plane_size = bm->BytesPerRow * bm->Rows; - short x_offset = (x >> 3); - - unsigned long dest = CARD_SCRATCH; - - WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); - WRITELONG(RTG_ADDR2, (unsigned long)dest); - WRITELONG(RTG_ADDR3, (unsigned long)bm->Planes[0]); - - for (unsigned short i = 0; i < bm->Depth; i++) { - if ((unsigned long)bm->Planes[i] == 0xFFFFFFFF) { - planemask |= (1 << i); - } - else if (bm->Planes[i] == NULL) { - planemask_0 |= (1 << i); - } - else { - unsigned long bmp_mem = (unsigned long)(bm->Planes[i]) + x_offset + (y * bm->BytesPerRow); - unsigned long plane_dest = dest; - for (unsigned short y_line = 0; y_line < h; y_line++) { - memcpy((unsigned char *)plane_dest, (unsigned char *)bmp_mem, bm->BytesPerRow); - plane_dest += bm->BytesPerRow; - bmp_mem += bm->BytesPerRow; - } - } - dest += plane_size; - } - - WRITESHORT(RTG_X1, x % 0x07); - WRITESHORT(RTG_X2, dx); - WRITESHORT(RTG_X3, w); - WRITESHORT(RTG_Y1, 0); - WRITESHORT(RTG_Y2, dy); - WRITESHORT(RTG_Y3, h); - - WRITESHORT(RTG_Y4, bm->Rows); - WRITESHORT(RTG_X4, r->BytesPerRow); - WRITESHORT(RTG_U1, planemask_0 << 8 | planemask); - WRITESHORT(RTG_U2, bm->BytesPerRow); - - WRITEBYTE(RTG_U81, mask); - WRITEBYTE(RTG_U82, minterm); - WRITEBYTE(RTG_U83, bm->Depth); - - WRITESHORT(RTG_COMMAND, RTGCMD_P2C); -} - -void BlitPlanar2Chunky (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bm), __REGA2(struct RenderInfo *r), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)) { - if (!b || !r) - return; - - uint32_t plane_size = bm->BytesPerRow * bm->Rows; - - uint32_t template_addr = CARD_SCRATCH; - - uint16_t plane_mask = mask; - uint8_t ff_mask = 0x00; - uint8_t cur_plane = 0x01; - - uint16_t line_size = (w >> 3) + 2; - uint32_t output_plane_size = line_size * h; - uint16_t x_offset = (x >> 3); - - WRITELONG(RTG_ADDR1, (unsigned long)r->Memory); - WRITELONG(RTG_ADDR2, template_addr); - WRITESHORT(RTG_X4, r->BytesPerRow); - WRITESHORT(RTG_X5, line_size); - WRITESHORT(RTG_FORMAT, rgbf_to_rtg[r->RGBFormat]); - - WRITEBYTE(RTG_U81, mask); - WRITEBYTE(RTG_U82, minterm); - - for (int16_t i = 0; i < bm->Depth; i++) { - uint16_t x_offset = (x >> 3); - if ((uint32_t)bm->Planes[i] == 0xFFFFFFFF) { - //memset((uint8_t*)(((uint32_t)b->memory)+template_addr), 0xFF, output_plane_size); - ff_mask |= cur_plane; - } - else if (bm->Planes[i] != NULL) { - uint8_t* bmp_mem = (uint8_t*)bm->Planes[i] + (y * bm->BytesPerRow) + x_offset; - uint8_t* dest = (uint8_t*)((uint32_t)template_addr); - for (int16_t y_line = 0; y_line < h; y_line++) { - memcpy(dest, bmp_mem, line_size); - dest += line_size; - bmp_mem += bm->BytesPerRow; - } - } - else { - plane_mask &= (cur_plane ^ 0xFF); - } - cur_plane <<= 1; - template_addr += output_plane_size; - } - - WRITESHORT(RTG_X1, (x & 0x07)); - WRITESHORT(RTG_X2, dx); - WRITESHORT(RTG_X3, w); - WRITESHORT(RTG_Y1, 0); - WRITESHORT(RTG_Y2, dy); - WRITESHORT(RTG_Y3, h); - - WRITESHORT(RTG_U1, (plane_mask << 8 | ff_mask)); - WRITEBYTE(RTG_U83, bm->Depth); - - WRITESHORT(RTG_COMMAND, RTGCMD_P2C); -} - -void BlitPlanar2Direct (__REGA0(struct BoardInfo *b), __REGA1(struct BitMap *bmp), __REGA2(struct RenderInfo *r), __REGA3(struct ColorIndexMapping *clut), __REGD0(SHORT x), __REGD1(SHORT y), __REGD2(SHORT dx), __REGD3(SHORT dy), __REGD4(SHORT w), __REGD5(SHORT h), __REGD6(UBYTE minterm), __REGD7(UBYTE mask)) { - -} diff --git a/platforms/amiga/rtg/rtg_driver_amiga/pigfx020.card b/platforms/amiga/rtg/rtg_driver_amiga/pigfx020.card index 054dfe2..2e04d11 100644 Binary files a/platforms/amiga/rtg/rtg_driver_amiga/pigfx020.card and b/platforms/amiga/rtg/rtg_driver_amiga/pigfx020.card differ diff --git a/platforms/amiga/rtg/rtg_driver_amiga/pigfx020i.card b/platforms/amiga/rtg/rtg_driver_amiga/pigfx020i.card new file mode 100644 index 0000000..0e70d1a Binary files /dev/null and b/platforms/amiga/rtg/rtg_driver_amiga/pigfx020i.card differ diff --git a/platforms/amiga/rtg/rtg_driver_amiga/pigfx030.card b/platforms/amiga/rtg/rtg_driver_amiga/pigfx030.card index 054dfe2..bfa07e5 100644 Binary files a/platforms/amiga/rtg/rtg_driver_amiga/pigfx030.card and b/platforms/amiga/rtg/rtg_driver_amiga/pigfx030.card differ diff --git a/platforms/amiga/rtg/rtg_driver_amiga/rtg_enums.h b/platforms/amiga/rtg/rtg_driver_amiga/rtg_enums.h deleted file mode 100644 index 6ee3a27..0000000 --- a/platforms/amiga/rtg/rtg_driver_amiga/rtg_enums.h +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: MIT - -// "Register" offsets for sending data to the RTG. -enum pi_regs { - RTG_COMMAND = 0x00, - RTG_X1 = 0x02, - RTG_X2 = 0x04, - RTG_X3 = 0x06, - RTG_Y1 = 0x08, - RTG_Y2 = 0x0A, - RTG_Y3 = 0x0C, - RTG_FORMAT = 0x0E, - RTG_RGB1 = 0x10, - RTG_RGB2 = 0x14, - RTG_ADDR1 = 0x18, - RTG_ADDR2 = 0x1C, - RTG_U81 = 0x20, - RTG_U82 = 0x21, - RTG_U83 = 0x22, - RTG_U84 = 0x23, - RTG_X4 = 0x24, - RTG_X5 = 0x26, - RTG_Y4 = 0x28, - RTG_Y5 = 0x2A, - RTG_U1 = 0x2C, - RTG_U2 = 0x2E, - RTG_ADDR3 = 0x30, - RTG_ADDR4 = 0x34, -}; - -enum rtg_cmds { - RTGCMD_SETGC, - RTGCMD_SETPAN, - RTGCMD_SETCLUT, - RTGCMD_ENABLE, - RTGCMD_SETDISPLAY, - RTGCMD_SETSWITCH, - RTGCMD_FILLRECT, - RTGCMD_BLITRECT, - RTGCMD_BLITRECT_NOMASK_COMPLETE, - RTGCMD_BLITPATTERN, - RTGCMD_BLITTEMPLATE, - RTGCMD_INVERTRECT, - RTGCMD_DRAWLINE, - RTGCMD_P2C, - RTGCMD_P2D, -}; - -enum rtg_formats { - RTGFMT_8BIT, - RTGFMT_RBG565, - RTGFMT_RGB32, - RTGFMT_RGB555, - RTGFMT_NUM, -}; - -enum gfx_minterm_modes { - MINTERM_FALSE, - MINTERM_NOR, - MINTERM_ONLYDST, - MINTERM_NOTSRC, - MINTERM_ONLYSRC, - MINTERM_INVERT, - MINTERM_EOR, - MINTERM_NAND, - MINTERM_AND, - MINTERM_NEOR, - MINTERM_DST, - MINTERM_NOTONLYSRC, - MINTERM_SRC, - MINTERM_NOTONLYDST, - MINTERM_OR, - MINTERM_TRUE, -}; - -enum gfx_draw_modes { - DRAWMODE_JAM1 = 0, - DRAWMODE_JAM2 = 1, - DRAWMODE_COMPLEMENT = 2, - DRAWMODE_INVERSVID = 4, -}; diff --git a/platforms/amiga/rtg/rtg_enums.h b/platforms/amiga/rtg/rtg_enums.h new file mode 100644 index 0000000..d25778e --- /dev/null +++ b/platforms/amiga/rtg/rtg_enums.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +// "Register" offsets for sending data to the RTG. +enum pi_regs { + RTG_COMMAND = 0x00, + RTG_X1 = 0x02, + RTG_X2 = 0x04, + RTG_X3 = 0x06, + RTG_Y1 = 0x08, + RTG_Y2 = 0x0A, + RTG_Y3 = 0x0C, + RTG_FORMAT = 0x0E, + RTG_RGB1 = 0x10, + RTG_RGB2 = 0x14, + RTG_ADDR1 = 0x18, + RTG_ADDR2 = 0x1C, + RTG_U81 = 0x20, + RTG_U82 = 0x21, + RTG_U83 = 0x22, + RTG_U84 = 0x23, + RTG_X4 = 0x24, + RTG_X5 = 0x26, + RTG_Y4 = 0x28, + RTG_Y5 = 0x2A, + RTG_U1 = 0x2C, + RTG_U2 = 0x2E, + RTG_ADDR3 = 0x30, + RTG_ADDR4 = 0x34, + RTG_DEBUGME = 0x50, + RTG_WAITVSYNC = 0x60, + RTG_INVBLANK = 0x62, + IRTG_COMMAND = 0x60, +}; + +enum rtg_cmds { + RTGCMD_SETGC, + RTGCMD_SETPAN, + RTGCMD_SETCLUT, + RTGCMD_ENABLE, + RTGCMD_SETDISPLAY, + RTGCMD_SETSWITCH, + RTGCMD_FILLRECT, + RTGCMD_BLITRECT, + RTGCMD_BLITRECT_NOMASK_COMPLETE, + RTGCMD_BLITPATTERN, + RTGCMD_BLITTEMPLATE, + RTGCMD_INVERTRECT, + RTGCMD_DRAWLINE, + RTGCMD_P2C, + RTGCMD_P2D, + RTGCMD_SETSPRITE, + RTGCMD_SETSPRITEPOS, + RTGCMD_SETSPRITECOLOR, + RTGCMD_SETSPRITEIMAGE, + RTGCMD_DEBUGME, +}; + +enum rtg_formats { + RTGFMT_8BIT, + RTGFMT_RBG565, + RTGFMT_RGB32, + RTGFMT_RGB555, + RTGFMT_NUM, +}; + +enum gfx_minterm_modes { + MINTERM_FALSE, + MINTERM_NOR, + MINTERM_ONLYDST, + MINTERM_NOTSRC, + MINTERM_ONLYSRC, + MINTERM_INVERT, + MINTERM_EOR, + MINTERM_NAND, + MINTERM_AND, + MINTERM_NEOR, + MINTERM_DST, + MINTERM_NOTONLYSRC, + MINTERM_SRC, + MINTERM_NOTONLYDST, + MINTERM_OR, + MINTERM_TRUE, +}; + +enum gfx_draw_modes { + DRAWMODE_JAM1 = 0, + DRAWMODE_JAM2 = 1, + DRAWMODE_COMPLEMENT = 2, + DRAWMODE_INVERSVID = 4, +}; + +static const unsigned short rgbf_to_rtg[16] = { + RTGFMT_8BIT, // 0x00 + RTGFMT_8BIT, // 0x01 + 0, // 0x02 + 0, // 0x03 + 0, // 0x04 + RTGFMT_RGB555, // 0x05 + 0, // 0x06 + 0, // 0x07 + RTGFMT_RGB32, // 0x08 + RTGFMT_RGB32, // 0x09 + RTGFMT_RBG565, // 0x0A + RTGFMT_RGB555, // 0x0B + 0, // 0x0C + RTGFMT_RGB555, // 0x0D + 0, // 0x0E + 0, // 0x0F +}; diff --git a/platforms/platforms.h b/platforms/platforms.h index 62dd044..a88bda8 100644 --- a/platforms/platforms.h +++ b/platforms/platforms.h @@ -11,3 +11,6 @@ enum base_platforms { }; struct platform_config *make_platform_config(char *name, char *subsys); + +void dump_range_to_file(uint32_t addr, uint32_t size, char *filename); +uint8_t *dump_range_to_memory(uint32_t addr, uint32_t size); diff --git a/platforms/shared/common.c b/platforms/shared/common.c new file mode 100644 index 0000000..d9aa87c --- /dev/null +++ b/platforms/shared/common.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include "platforms/platforms.h" +#include "gpio/ps_protocol.h" + +void dump_range_to_file(uint32_t addr, uint32_t size, char *filename) { + FILE *out = fopen(filename, "wb+"); + if (out == NULL) { + printf ("[SHARED-DUMP_RANGE_TO_FILE] Failed to open %s for writing.\n", filename); + printf ("[SHARED-DUMP_RANGE_TO_FILE] Memory range has not been dumped to file.\n"); + return; + } + + for (uint32_t i = 0; i < size; i += 2) { + uint16_t in = be16toh(read16(addr + i)); + fwrite(&in, 2, 1, out); + } + + fclose(out); + printf ("[SHARED-DUMP_RANGE_TO_FILE] Memory range dumped to file %s.\n", filename); +} + +uint8_t *dump_range_to_memory(uint32_t addr, uint32_t size) { + uint8_t *mem = calloc(size, 1); + + if (mem == NULL) { + printf ("[SHARED-DUMP_RANGE_TO_MEMORY] Failed to allocate memory for dumped range.\n"); + return NULL; + } + + for (uint32_t i = 0; i < size; i += 2) { + *(uint16_t *)&mem[i] = (uint16_t)be16toh(read16(addr + i)); + } + + printf ("[SHARED-DUMP_RANGE_TO_FILE] Memory range copied to RAM.\n"); + return mem; +} diff --git a/raylib/camera.h b/raylib/camera.h new file mode 100644 index 0000000..cd42b54 --- /dev/null +++ b/raylib/camera.h @@ -0,0 +1,522 @@ +/******************************************************************************************* +* +* raylib.camera - Camera system with multiple modes support +* +* NOTE: Memory footprint of this library is aproximately 52 bytes (global variables) +* +* CONFIGURATION: +* +* #define CAMERA_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define CAMERA_STANDALONE +* If defined, the library can be used as standalone as a camera system but some +* functions must be redefined to manage inputs accordingly. +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, update and maintenance +* Marc Palau: Initial implementation (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef CAMERA_H +#define CAMERA_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for CAMERA_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(CAMERA_STANDALONE) + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Camera type, defines a camera position/orientation in 3d space + typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view apperture in Y (degrees) in perspective, used as near plane width in orthographic + int type; // Camera type, defines projection type: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC + } Camera3D; + + typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + + // Camera system modes + typedef enum { + CAMERA_CUSTOM = 0, + CAMERA_FREE, + CAMERA_ORBITAL, + CAMERA_FIRST_PERSON, + CAMERA_THIRD_PERSON + } CameraMode; + + // Camera projection modes + typedef enum { + CAMERA_PERSPECTIVE = 0, + CAMERA_ORTHOGRAPHIC + } CameraProjection; +#endif + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(CAMERA_STANDALONE) +void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) +void UpdateCamera(Camera *camera); // Update camera position for selected mode + +void SetCameraPanControl(int keyPan); // Set camera pan key to combine with mouse movement (free camera) +void SetCameraAltControl(int keyAlt); // Set camera alt key to combine with mouse movement (free camera) +void SetCameraSmoothZoomControl(int szoomKey); // Set camera smooth zoom key to combine with mouse (free camera) +void SetCameraMoveControls(int keyFront, int keyBack, + int keyRight, int keyLeft, + int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) +#endif + +#ifdef __cplusplus +} +#endif + +#endif // CAMERA_H + + +/*********************************************************************************** +* +* CAMERA IMPLEMENTATION +* +************************************************************************************/ + +#if defined(CAMERA_IMPLEMENTATION) + +#include // Required for: sinf(), cosf(), sqrtf() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846 +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Camera mouse movement sensitivity +#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f +#define CAMERA_MOUSE_SCROLL_SENSITIVITY 1.5f + +// FREE_CAMERA +#define CAMERA_FREE_MOUSE_SENSITIVITY 0.01f +#define CAMERA_FREE_DISTANCE_MIN_CLAMP 0.3f +#define CAMERA_FREE_DISTANCE_MAX_CLAMP 120.0f +#define CAMERA_FREE_MIN_CLAMP 85.0f +#define CAMERA_FREE_MAX_CLAMP -85.0f +#define CAMERA_FREE_SMOOTH_ZOOM_SENSITIVITY 0.05f +#define CAMERA_FREE_PANNING_DIVIDER 5.1f + +// ORBITAL_CAMERA +#define CAMERA_ORBITAL_SPEED 0.01f // Radians per frame + +// FIRST_PERSON +//#define CAMERA_FIRST_PERSON_MOUSE_SENSITIVITY 0.003f +#define CAMERA_FIRST_PERSON_FOCUS_DISTANCE 25.0f +#define CAMERA_FIRST_PERSON_MIN_CLAMP 89.0f +#define CAMERA_FIRST_PERSON_MAX_CLAMP -89.0f + +#define CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER 8.0f +#define CAMERA_FIRST_PERSON_STEP_DIVIDER 30.0f +#define CAMERA_FIRST_PERSON_WAVING_DIVIDER 200.0f + +// THIRD_PERSON +//#define CAMERA_THIRD_PERSON_MOUSE_SENSITIVITY 0.003f +#define CAMERA_THIRD_PERSON_DISTANCE_CLAMP 1.2f +#define CAMERA_THIRD_PERSON_MIN_CLAMP 5.0f +#define CAMERA_THIRD_PERSON_MAX_CLAMP -85.0f +#define CAMERA_THIRD_PERSON_OFFSET (Vector3){ 0.4f, 0.0f, 0.0f } + +// PLAYER (used by camera) +#define PLAYER_MOVEMENT_SENSITIVITY 20.0f + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Camera move modes (first person and third person cameras) +typedef enum { + MOVE_FRONT = 0, + MOVE_BACK, + MOVE_RIGHT, + MOVE_LEFT, + MOVE_UP, + MOVE_DOWN +} CameraMove; + +// Camera global state context data [56 bytes] +typedef struct { + unsigned int mode; // Current camera mode + float targetDistance; // Camera distance from position to target + float playerEyesPosition; // Player eyes position from ground (in meters) + Vector2 angle; // Camera angle in plane XZ + + // Camera movement control keys + int moveControl[6]; // Move controls (CAMERA_FIRST_PERSON) + int smoothZoomControl; // Smooth zoom control key + int altControl; // Alternative control key + int panControl; // Pan view control key +} CameraData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static CameraData CAMERA = { // Global CAMERA state context + .mode = 0, + .targetDistance = 0, + .playerEyesPosition = 1.85f, + .angle = { 0 }, + .moveControl = { 'W', 'S', 'D', 'A', 'E', 'Q' }, + .smoothZoomControl = 341, // raylib: KEY_LEFT_CONTROL + .altControl = 342, // raylib: KEY_LEFT_ALT + .panControl = 2 // raylib: MOUSE_MIDDLE_BUTTON +}; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(CAMERA_STANDALONE) +// NOTE: Camera controls depend on some raylib input functions +static void EnableCursor() {} // Unlock cursor +static void DisableCursor() {} // Lock cursor + +static int IsKeyDown(int key) { return 0; } + +static int IsMouseButtonDown(int button) { return 0;} +static float GetMouseWheelMove() { return 0.0f; } +static Vector2 GetMousePosition() { return (Vector2){ 0.0f, 0.0f }; } +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Select camera mode (multiple camera modes available) +void SetCameraMode(Camera camera, int mode) +{ + Vector3 v1 = camera.position; + Vector3 v2 = camera.target; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + + CAMERA.targetDistance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance to target + + // Camera angle calculation + CAMERA.angle.x = atan2f(dx, dz); // Camera angle in plane XZ (0 aligned with Z, move positive CCW) + CAMERA.angle.y = atan2f(dy, sqrtf(dx*dx + dz*dz)); // Camera angle in plane XY (0 aligned with X, move positive CW) + + CAMERA.playerEyesPosition = camera.position.y; // Init player eyes position to camera Y position + + // Lock cursor for first person and third person cameras + if ((mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON)) DisableCursor(); + else EnableCursor(); + + CAMERA.mode = mode; +} + +// Update camera depending on selected mode +// NOTE: Camera controls depend on some raylib functions: +// System: EnableCursor(), DisableCursor() +// Mouse: IsMouseButtonDown(), GetMousePosition(), GetMouseWheelMove() +// Keys: IsKeyDown() +// TODO: Port to quaternion-based camera (?) +void UpdateCamera(Camera *camera) +{ + static int swingCounter = 0; // Used for 1st person swinging movement + static Vector2 previousMousePosition = { 0.0f, 0.0f }; + + // TODO: Compute CAMERA.targetDistance and CAMERA.angle here (?) + + // Mouse movement detection + Vector2 mousePositionDelta = { 0.0f, 0.0f }; + Vector2 mousePosition = GetMousePosition(); + float mouseWheelMove = GetMouseWheelMove(); + + // Keys input detection + // TODO: Input detection is raylib-dependant, it could be moved outside the module + bool keyPan = IsMouseButtonDown(CAMERA.panControl); + bool keyAlt = IsKeyDown(CAMERA.altControl); + bool szoomKey = IsKeyDown(CAMERA.smoothZoomControl); + bool direction[6] = { IsKeyDown(CAMERA.moveControl[MOVE_FRONT]), + IsKeyDown(CAMERA.moveControl[MOVE_BACK]), + IsKeyDown(CAMERA.moveControl[MOVE_RIGHT]), + IsKeyDown(CAMERA.moveControl[MOVE_LEFT]), + IsKeyDown(CAMERA.moveControl[MOVE_UP]), + IsKeyDown(CAMERA.moveControl[MOVE_DOWN]) }; + + if (CAMERA.mode != CAMERA_CUSTOM) + { + mousePositionDelta.x = mousePosition.x - previousMousePosition.x; + mousePositionDelta.y = mousePosition.y - previousMousePosition.y; + + previousMousePosition = mousePosition; + } + + // Support for multiple automatic camera modes + // NOTE: In case of CAMERA_CUSTOM nothing happens here, user must update it manually + switch (CAMERA.mode) + { + case CAMERA_FREE: // Camera free controls, using standard 3d-content-creation scheme + { + // Camera zoom + if ((CAMERA.targetDistance < CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) + { + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (CAMERA.targetDistance > CAMERA_FREE_DISTANCE_MAX_CLAMP) CAMERA.targetDistance = CAMERA_FREE_DISTANCE_MAX_CLAMP; + } + + // Camera looking down + else if ((camera->position.y > camera->target.y) && (CAMERA.targetDistance == CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y >= 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + + // if (camera->target.y < 0) camera->target.y = -0.001; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y < 0) && (mouseWheelMove > 0)) + { + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (CAMERA.targetDistance < CAMERA_FREE_DISTANCE_MIN_CLAMP) CAMERA.targetDistance = CAMERA_FREE_DISTANCE_MIN_CLAMP; + } + // Camera looking up + else if ((camera->position.y < camera->target.y) && (CAMERA.targetDistance == CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y <= 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + + // if (camera->target.y > 0) camera->target.y = 0.001; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y > 0) && (mouseWheelMove > 0)) + { + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (CAMERA.targetDistance < CAMERA_FREE_DISTANCE_MIN_CLAMP) CAMERA.targetDistance = CAMERA_FREE_DISTANCE_MIN_CLAMP; + } + + // Input keys checks + if (keyPan) + { + if (keyAlt) // Alternative key behaviour + { + if (szoomKey) + { + // Camera smooth zoom + CAMERA.targetDistance += (mousePositionDelta.y*CAMERA_FREE_SMOOTH_ZOOM_SENSITIVITY); + } + else + { + // Camera rotation + CAMERA.angle.x += mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY; + CAMERA.angle.y += mousePositionDelta.y*-CAMERA_FREE_MOUSE_SENSITIVITY; + + // Angle clamp + if (CAMERA.angle.y > CAMERA_FREE_MIN_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FREE_MIN_CLAMP*DEG2RAD; + else if (CAMERA.angle.y < CAMERA_FREE_MAX_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FREE_MAX_CLAMP*DEG2RAD; + } + } + else + { + // Camera panning + camera->target.x += ((mousePositionDelta.x*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.y += ((mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.z += ((mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + } + } + + // Update camera position with changes + camera->position.x = -sinf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.x; + camera->position.y = -sinf(CAMERA.angle.y)*CAMERA.targetDistance + camera->target.y; + camera->position.z = -cosf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.z; + + } break; + case CAMERA_ORBITAL: // Camera just orbits around target, only zoom allowed + { + CAMERA.angle.x += CAMERA_ORBITAL_SPEED; // Camera orbit angle + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); // Camera zoom + + // Camera distance clamp + if (CAMERA.targetDistance < CAMERA_THIRD_PERSON_DISTANCE_CLAMP) CAMERA.targetDistance = CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + + // Update camera position with changes + camera->position.x = sinf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.x; + camera->position.y = ((CAMERA.angle.y <= 0.0f)? 1 : -1)*sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; + camera->position.z = cosf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.z; + + } break; + case CAMERA_FIRST_PERSON: // Camera moves as in a first-person game, controls are configurable + { + camera->position.x += (sinf(CAMERA.angle.x)*direction[MOVE_BACK] - + sinf(CAMERA.angle.x)*direction[MOVE_FRONT] - + cosf(CAMERA.angle.x)*direction[MOVE_LEFT] + + cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.y += (sinf(CAMERA.angle.y)*direction[MOVE_FRONT] - + sinf(CAMERA.angle.y)*direction[MOVE_BACK] + + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.z += (cosf(CAMERA.angle.x)*direction[MOVE_BACK] - + cosf(CAMERA.angle.x)*direction[MOVE_FRONT] + + sinf(CAMERA.angle.x)*direction[MOVE_LEFT] - + sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + // Camera orientation calculation + CAMERA.angle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); + CAMERA.angle.y += (mousePositionDelta.y*-CAMERA_MOUSE_MOVE_SENSITIVITY); + + // Angle clamp + if (CAMERA.angle.y > CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD; + else if (CAMERA.angle.y < CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD; + + // Recalculate camera target considering translation and rotation + Matrix translation = MatrixTranslate(0, 0, (CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER)); + Matrix rotation = MatrixRotateXYZ((Vector3){ PI*2 - CAMERA.angle.y, PI*2 - CAMERA.angle.x, 0 }); + Matrix transform = MatrixMultiply(translation, rotation); + + camera->target.x = camera->position.x - transform.m12; + camera->target.y = camera->position.y - transform.m13; + camera->target.z = camera->position.z - transform.m14; + + // If movement detected (some key pressed), increase swinging + for (int i = 0; i < 6; i++) if (direction[i]) { swingCounter++; break; } + + // Camera position update + // NOTE: On CAMERA_FIRST_PERSON player Y-movement is limited to player 'eyes position' + camera->position.y = CAMERA.playerEyesPosition - sinf(swingCounter/CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER)/CAMERA_FIRST_PERSON_STEP_DIVIDER; + + camera->up.x = sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; + camera->up.z = -sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; + + } break; + case CAMERA_THIRD_PERSON: // Camera moves as in a third-person game, following target at a distance, controls are configurable + { + camera->position.x += (sinf(CAMERA.angle.x)*direction[MOVE_BACK] - + sinf(CAMERA.angle.x)*direction[MOVE_FRONT] - + cosf(CAMERA.angle.x)*direction[MOVE_LEFT] + + cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.y += (sinf(CAMERA.angle.y)*direction[MOVE_FRONT] - + sinf(CAMERA.angle.y)*direction[MOVE_BACK] + + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.z += (cosf(CAMERA.angle.x)*direction[MOVE_BACK] - + cosf(CAMERA.angle.x)*direction[MOVE_FRONT] + + sinf(CAMERA.angle.x)*direction[MOVE_LEFT] - + sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + // Camera orientation calculation + CAMERA.angle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); + CAMERA.angle.y += (mousePositionDelta.y*-CAMERA_MOUSE_MOVE_SENSITIVITY); + + // Angle clamp + if (CAMERA.angle.y > CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD; + else if (CAMERA.angle.y < CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD; + + // Camera zoom + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + + // Camera distance clamp + if (CAMERA.targetDistance < CAMERA_THIRD_PERSON_DISTANCE_CLAMP) CAMERA.targetDistance = CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + + // TODO: It seems camera->position is not correctly updated or some rounding issue makes the camera move straight to camera->target... + camera->position.x = sinf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.x; + + if (CAMERA.angle.y <= 0.0f) camera->position.y = sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; + else camera->position.y = -sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; + + camera->position.z = cosf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.z; + + } break; + case CAMERA_CUSTOM: break; + default: break; + } +} + +// Set camera pan key to combine with mouse movement (free camera) +void SetCameraPanControl(int keyPan) { CAMERA.panControl = keyPan; } + +// Set camera alt key to combine with mouse movement (free camera) +void SetCameraAltControl(int keyAlt) { CAMERA.altControl = keyAlt; } + +// Set camera smooth zoom key to combine with mouse (free camera) +void SetCameraSmoothZoomControl(int szoomKey) { CAMERA.smoothZoomControl = szoomKey; } + +// Set camera move controls (1st person and 3rd person cameras) +void SetCameraMoveControls(int keyFront, int keyBack, int keyRight, int keyLeft, int keyUp, int keyDown) +{ + CAMERA.moveControl[MOVE_FRONT] = keyFront; + CAMERA.moveControl[MOVE_BACK] = keyBack; + CAMERA.moveControl[MOVE_RIGHT] = keyRight; + CAMERA.moveControl[MOVE_LEFT] = keyLeft; + CAMERA.moveControl[MOVE_UP] = keyUp; + CAMERA.moveControl[MOVE_DOWN] = keyDown; +} + +#endif // CAMERA_IMPLEMENTATION diff --git a/raylib/config.h b/raylib/config.h new file mode 100644 index 0000000..99cb1d4 --- /dev/null +++ b/raylib/config.h @@ -0,0 +1,213 @@ +/********************************************************************************************** +* +* raylib configuration flags +* +* This file defines all the configuration flags for the different raylib modules +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2018-2021 Ahmad Fatoum & Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#define RAYLIB_VERSION "3.7" + +//------------------------------------------------------------------------------------ +// Module: core - Configuration Flags +//------------------------------------------------------------------------------------ +// Camera module is included (camera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital +#define SUPPORT_CAMERA_SYSTEM 1 +// Gestures module is included (gestures.h) to support gestures detection: tap, hold, swipe, drag +#define SUPPORT_GESTURES_SYSTEM 1 +// Mouse gestures are directly mapped like touches and processed by gestures system +#define SUPPORT_MOUSE_GESTURES 1 +// Reconfigure standard input to receive key inputs, works with SSH connection. +#define SUPPORT_SSH_KEYBOARD_RPI 1 +// Draw a mouse pointer on screen +#define SUPPORT_MOUSE_CURSOR_NATIVE 1 +// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. +// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. +#define SUPPORT_WINMM_HIGHRES_TIMER 1 +// Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used +//#define SUPPORT_BUSY_WAIT_LOOP 1 +// Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end +#define SUPPORT_HALFBUSY_WAIT_LOOP +// Wait for events passively (sleeping while no events) instead of polling them actively every frame +//#define SUPPORT_EVENTS_WAITING 1 +// Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() +#define SUPPORT_SCREEN_CAPTURE 1 +// Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() +#define SUPPORT_GIF_RECORDING 1 +// Support CompressData() and DecompressData() functions +#define SUPPORT_COMPRESSION_API 1 +// Support saving binary data automatically to a generated storage.data file. This file is managed internally. +#define SUPPORT_DATA_STORAGE 1 + +// core: Configuration values +//------------------------------------------------------------------------------------ +#if defined(__linux__) + #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) +#else + #define MAX_FILEPATH_LENGTH 512 // Maximum length supported for filepaths +#endif + +#define MAX_GAMEPADS 4 // Max number of gamepads supported +#define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) +#define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) +#define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported +#define MAX_KEY_PRESSED_QUEUE 16 // Max number of characters in the key input queue + +#define STORAGE_DATA_FILE "storage.data" // Automatic storage filename + +#define MAX_DECOMPRESSION_SIZE 64 // Max size allocated for decompression in MB + + +//------------------------------------------------------------------------------------ +// Module: rlgl - Configuration values +//------------------------------------------------------------------------------------ +// Show OpenGL extensions and capabilities detailed logs on init +//#define SUPPORT_GL_DETAILS_INFO 1 + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + #define DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch limits +#elif defined(GRAPHICS_API_OPENGL_ES2) + #define DEFAULT_BATCH_BUFFER_ELEMENTS 2048 // Default internal render batch limits +#endif + +#define DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#define DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) + +#define MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +#define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#define MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported + +#define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance +#define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance + +// Default shader vertex attribute names to set location points +#define DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Binded by default to shader location: 0 +#define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Binded by default to shader location: 1 +#define DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Binded by default to shader location: 2 +#define DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Binded by default to shader location: 3 +#define DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Binded by default to shader location: 4 +#define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Binded by default to shader location: 5 + + +//------------------------------------------------------------------------------------ +// Module: shapes - Configuration Flags +//------------------------------------------------------------------------------------ +// Use QUADS instead of TRIANGLES for drawing when possible +// Some lines-based shapes could still use lines +#define SUPPORT_QUADS_DRAW_MODE 1 + + +//------------------------------------------------------------------------------------ +// Module: textures - Configuration Flags +//------------------------------------------------------------------------------------ +// Selecte desired fileformats to be supported for image data loading +#define SUPPORT_FILEFORMAT_PNG 1 +//#define SUPPORT_FILEFORMAT_BMP 1 +//#define SUPPORT_FILEFORMAT_TGA 1 +//#define SUPPORT_FILEFORMAT_JPG 1 +#define SUPPORT_FILEFORMAT_GIF 1 +//#define SUPPORT_FILEFORMAT_PSD 1 +#define SUPPORT_FILEFORMAT_DDS 1 +#define SUPPORT_FILEFORMAT_HDR 1 +//#define SUPPORT_FILEFORMAT_KTX 1 +//#define SUPPORT_FILEFORMAT_ASTC 1 +//#define SUPPORT_FILEFORMAT_PKM 1 +//#define SUPPORT_FILEFORMAT_PVR 1 + +// Support image export functionality (.png, .bmp, .tga, .jpg) +#define SUPPORT_IMAGE_EXPORT 1 +// Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) +#define SUPPORT_IMAGE_GENERATION 1 +// Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... +// If not defined, still some functions are supported: ImageFormat(), ImageCrop(), ImageToPOT() +#define SUPPORT_IMAGE_MANIPULATION 1 + + +//------------------------------------------------------------------------------------ +// Module: text - Configuration Flags +//------------------------------------------------------------------------------------ +// Default font is loaded on window initialization to be available for the user to render simple text +// NOTE: If enabled, uses external module functions to load default raylib font +#define SUPPORT_DEFAULT_FONT 1 +// Selected desired font fileformats to be supported for loading +#define SUPPORT_FILEFORMAT_FNT 1 +#define SUPPORT_FILEFORMAT_TTF 1 + +// Support text management functions +// If not defined, still some functions are supported: TextLength(), TextFormat() +#define SUPPORT_TEXT_MANIPULATION 1 + +// text: Configuration values +//------------------------------------------------------------------------------------ +#define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions: + // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal(), TextSplit() +#define MAX_TEXT_UNICODE_CHARS 512 // Maximum number of unicode codepoints: GetCodepoints() +#define MAX_TEXTSPLIT_COUNT 128 // Maximum number of substrings to split: TextSplit() + + +//------------------------------------------------------------------------------------ +// Module: models - Configuration Flags +//------------------------------------------------------------------------------------ +// Selected desired model fileformats to be supported for loading +#define SUPPORT_FILEFORMAT_OBJ 1 +#define SUPPORT_FILEFORMAT_MTL 1 +#define SUPPORT_FILEFORMAT_IQM 1 +#define SUPPORT_FILEFORMAT_GLTF 1 +// Support procedural mesh generation functions, uses external par_shapes.h library +// NOTE: Some generated meshes DO NOT include generated texture coordinates +#define SUPPORT_MESH_GENERATION 1 + + +//------------------------------------------------------------------------------------ +// Module: audio - Configuration Flags +//------------------------------------------------------------------------------------ +// Desired audio fileformats to be supported for loading +#define SUPPORT_FILEFORMAT_WAV 1 +#define SUPPORT_FILEFORMAT_OGG 1 +#define SUPPORT_FILEFORMAT_XM 1 +#define SUPPORT_FILEFORMAT_MOD 1 +#define SUPPORT_FILEFORMAT_MP3 1 +//#define SUPPORT_FILEFORMAT_FLAC 1 + +// audio: Configuration values +//------------------------------------------------------------------------------------ +#define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (miniaudio: float-32bit) +#define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo +#define AUDIO_DEVICE_SAMPLE_RATE 0 // Device sample rate (device default) + +#define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Maximum number of audio pool channels + +//------------------------------------------------------------------------------------ +// Module: utils - Configuration Flags +//------------------------------------------------------------------------------------ +// Standard file io library (stdio.h) included +#define SUPPORT_STANDARD_FILEIO +// Show TRACELOG() output messages +// NOTE: By default LOG_DEBUG traces not shown +#define SUPPORT_TRACELOG 1 +//#define SUPPORT_TRACELOG_DEBUG 1 + +// utils: Configuration values +//------------------------------------------------------------------------------------ +#define MAX_TRACELOG_MSG_LENGTH 128 // Max length of one trace-log message +#define MAX_UWP_MESSAGES 512 // Max UWP messages to process diff --git a/raylib/core.c b/raylib/core.c new file mode 100644 index 0000000..a6a8505 --- /dev/null +++ b/raylib/core.c @@ -0,0 +1,6611 @@ +/********************************************************************************************** +* +* raylib.core - Basic functions to manage windows, OpenGL context and input on multiple platforms +* +* PLATFORMS SUPPORTED: +* - PLATFORM_DESKTOP: Windows (Win32, Win64) +* - PLATFORM_DESKTOP: Linux (X11 desktop mode) +* - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) +* - PLATFORM_DESKTOP: OSX/macOS +* - PLATFORM_ANDROID: Android 4.0 (ARM, ARM64) +* - PLATFORM_RPI: Raspberry Pi 0,1,2,3,4 (Raspbian) +* - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver +* - PLATFORM_WEB: HTML5 with asm.js (Chrome, Firefox) +* - PLATFORM_UWP: Windows 10 App, Windows Phone, Xbox One +* +* CONFIGURATION: +* +* #define PLATFORM_DESKTOP +* Windowing and input system configured for desktop platforms: Windows, Linux, OSX, FreeBSD, OpenBSD, NetBSD, DragonFly +* NOTE: Oculus Rift CV1 requires PLATFORM_DESKTOP for mirror rendering - View [rlgl] module to enable it +* +* #define PLATFORM_ANDROID +* Windowing and input system configured for Android device, app activity managed internally in this module. +* NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL +* +* #define PLATFORM_RPI +* Windowing and input system configured for Raspberry Pi i native mode (no X.org required, tested on Raspbian), +* graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ +* +* #define PLATFORM_WEB +* Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js +* using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. +* +* #define PLATFORM_UWP +* Universal Windows Platform support, using OpenGL ES 2.0 through ANGLE on multiple Windows platforms, +* including Windows 10 App, Windows Phone and Xbox One platforms. +* +* #define SUPPORT_DEFAULT_FONT (default) +* Default font is loaded on window initialization to be available for the user to render simple text. +* NOTE: If enabled, uses external module functions to load default raylib font (module: text) +* +* #define SUPPORT_CAMERA_SYSTEM +* Camera module is included (camera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital +* +* #define SUPPORT_GESTURES_SYSTEM +* Gestures module is included (gestures.h) to support gestures detection: tap, hold, swipe, drag +* +* #define SUPPORT_MOUSE_GESTURES +* Mouse gestures are directly mapped like touches and processed by gestures system. +* +* #define SUPPORT_TOUCH_AS_MOUSE +* Touch input and mouse input are shared. Mouse functions also return touch information. +* +* #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) +* Reconfigure standard input to receive key inputs, works with SSH connection. +* WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other running processes or +* blocking the device is not restored properly. Use with care. +* +* #define SUPPORT_MOUSE_CURSOR_NATIVE (Raspberry Pi and DRM only) +* Draw a mouse pointer on screen +* +* #define SUPPORT_BUSY_WAIT_LOOP +* Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used +* +* #define SUPPORT_HALFBUSY_WAIT_LOOP +* Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end +* +* #define SUPPORT_EVENTS_WAITING +* Wait for events passively (sleeping while no events) instead of polling them actively every frame +* +* #define SUPPORT_SCREEN_CAPTURE +* Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() +* +* #define SUPPORT_GIF_RECORDING +* Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() +* +* #define SUPPORT_COMPRESSION_API +* Support CompressData() and DecompressData() functions, those functions use zlib implementation +* provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module +* for linkage +* +* #define SUPPORT_DATA_STORAGE +* Support saving binary data automatically to a generated storage.data file. This file is managed internally +* +* +* DEPENDENCIES: +* rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly) +* raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) +* camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) +* gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#else + #define RAYLIB_VERSION "3.7" +#endif + +#include "utils.h" // Required for: TRACELOG macros + +#if (defined(__linux__) || defined(PLATFORM_WEB)) && _POSIX_C_SOURCE < 199309L + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. +#endif + +#define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation of raymath here +#include "raymath.h" // Required for: Vector3 and Matrix functions + +#define RLGL_IMPLEMENTATION +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 + +#if defined(SUPPORT_GESTURES_SYSTEM) + #define GESTURES_IMPLEMENTATION + #include "gestures.h" // Gestures detection functionality +#endif + +#if defined(SUPPORT_CAMERA_SYSTEM) + #define CAMERA_IMPLEMENTATION + #include "camera.h" // Camera system functionality +#endif + +#if defined(SUPPORT_GIF_RECORDING) + //#define MSF_GIF_MALLOC RL_MALLOC + //#define MSF_GIF_FREE RL_FREE + + #define MSF_GIF_IMPL + #include "external/msf_gif.h" // Support GIF recording +#endif + +#if defined(SUPPORT_COMPRESSION_API) + #define SINFL_IMPLEMENTATION + #include "external/sinfl.h" + + #define SDEFL_IMPLEMENTATION + #include "external/sdefl.h" +#endif + +#include // Required for: srand(), rand(), atexit() +#include // Required for: sprintf() [Used in OpenURL()] +#include // Required for: strrchr(), strcmp(), strlen() +#include // Required for: time() [Used in InitTimer()] +#include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] + +#include // Required for: stat() [Used in GetFileModTime()] + +#if (defined(PLATFORM_DESKTOP) || defined(PLATFORM_UWP)) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) + #define DIRENT_MALLOC RL_MALLOC + #define DIRENT_FREE RL_FREE + + #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] +#else + #include // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] +#endif + +#if defined(_WIN32) + #include // Required for: _getch(), _chdir() + #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() + #define CHDIR _chdir + #include // Required for _access() [Used in FileExists()] +#else + #include // Required for: getch(), chdir() (POSIX), access() + #define GETCWD getcwd + #define CHDIR chdir +#endif + +#if defined(PLATFORM_DESKTOP) + #define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 + // NOTE: Already provided by rlgl implementation (on glad.h) + #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management + // NOTE: GLFW3 already includes gl.h (OpenGL) headers + + // Support retrieving native window handlers + #if defined(_WIN32) + #define GLFW_EXPOSE_NATIVE_WIN32 + #include "GLFW/glfw3native.h" // WARNING: It requires customization to avoid windows.h inclusion! + + #if defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) + // NOTE: Those functions require linking with winmm library + unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); + unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); + #endif + #endif + #if defined(__linux__) || defined(__FreeBSD__) + #include // Required for: timespec, nanosleep(), select() - POSIX + + //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type + //#define GLFW_EXPOSE_NATIVE_WAYLAND + //#define GLFW_EXPOSE_NATIVE_MIR + #include "GLFW/glfw3native.h" // Required for: glfwGetX11Window() + #endif + #if defined(__APPLE__) + #include // Required for: usleep() + + //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition + #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() + #endif +#endif + +#if defined(PLATFORM_ANDROID) + //#include // Android sensors functions (accelerometer, gyroscope, light...) + #include // Defines AWINDOW_FLAG_FULLSCREEN and others + #include // Defines basic app state struct and manages activity + + #include // Native platform windowing system interface + //#include // OpenGL ES 2.0 library (not required in this module) +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + #include // POSIX file control definitions - open(), creat(), fcntl() + #include // POSIX standard function definitions - read(), close(), STDIN_FILENO + #include // POSIX terminal control definitions - tcgetattr(), tcsetattr() + #include // POSIX threads management (inputs reading) + #include // POSIX directory browsing + + #include // UNIX System call for device-specific input/output operations - ioctl() + #include // Linux: KDSKBMODE, K_MEDIUMRAM constants definition + #include // Linux: Keycodes constants definition (KEY_A, ...) + #include // Linux: Joystick support library + +#if defined(PLATFORM_RPI) + #include "bcm_host.h" // Raspberry Pi VideoCore IV access functions +#endif + +#if defined(PLATFORM_DRM) + #include // Generic Buffer Management + #include // Direct Rendering Manager user-level library interface + #include // Direct Rendering Manager modesetting interface +#endif + + #include "EGL/egl.h" // Native platform windowing system interface + #include "EGL/eglext.h" // EGL extensions + //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module) +#endif + +#if defined(PLATFORM_UWP) + #include "EGL/egl.h" // Native platform windowing system interface + #include "EGL/eglext.h" // EGL extensions + //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module) + #include "uwp_events.h" // UWP bootstrapping functions +#endif + +#if defined(PLATFORM_WEB) + #define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL) + #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management + #include // Required for: timespec, nanosleep(), select() - POSIX + + #include // Emscripten library - LLVM to JavaScript compiler + #include // Emscripten HTML5 library +#endif + +#if defined(SUPPORT_COMPRESSION_API) + // NOTE: Those declarations require stb_image and stb_image_write definitions, included in textures module + unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); + char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen); +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event number + + #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) + #define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events +#endif + +#ifndef MAX_FILEPATH_LENGTH + #if defined(__linux__) + #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) + #else + #define MAX_FILEPATH_LENGTH 512 // Maximum length supported for filepaths + #endif +#endif + +#ifndef MAX_GAMEPADS + #define MAX_GAMEPADS 4 // Max number of gamepads supported +#endif +#ifndef MAX_GAMEPAD_AXIS + #define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) +#endif +#ifndef MAX_GAMEPAD_BUTTONS + #define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) +#endif +#ifndef MAX_TOUCH_POINTS + #define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported +#endif +#ifndef MAX_KEY_PRESSED_QUEUE + #define MAX_KEY_PRESSED_QUEUE 16 // Max number of keys in the key input queue +#endif +#ifndef MAX_CHAR_PRESSED_QUEUE + #define MAX_CHAR_PRESSED_QUEUE 16 // Max number of characters in the char input queue +#endif + +#if defined(SUPPORT_DATA_STORAGE) + #ifndef STORAGE_DATA_FILE + #define STORAGE_DATA_FILE "storage.data" // Automatic storage filename + #endif +#endif + +#ifndef MAX_DECOMPRESSION_SIZE + #define MAX_DECOMPRESSION_SIZE 64 // Max size allocated for decompression in MB +#endif + +// Flags operation macros +#define FLAG_SET(n, f) ((n) |= (f)) +#define FLAG_CLEAR(n, f) ((n) &= ~(f)) +#define FLAG_TOGGLE(n, f) ((n) ^= (f)) +#define FLAG_CHECK(n, f) ((n) & (f)) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +typedef struct { + pthread_t threadId; // Event reading thread id + int fd; // File descriptor to the device it is assigned to + int eventNum; // Number of 'event' device + Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) + int touchSlot; // Hold the touch slot number of the currently being sent multitouch block + bool isMouse; // True if device supports relative X Y movements + bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH + bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH + bool isKeyboard; // True if device has letter keycodes + bool isGamepad; // True if device has gamepad buttons +} InputEventWorker; +#endif + +typedef struct { int x; int y; } Point; +typedef struct { unsigned int width; unsigned int height; } Size; + +// Core global state context data +typedef struct CoreData { + struct { +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + GLFWwindow *handle; // Native window handle (graphic device) +#endif +#if defined(PLATFORM_RPI) + EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device) +#endif +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) +#if defined(PLATFORM_DRM) + int fd; // /dev/dri/... file descriptor + drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector + int modeIndex; // index of the used mode of connector->modes + drmModeCrtc *crtc; // crt controller + struct gbm_device *gbmDevice; // device of Generic Buffer Management (GBM, native platform for EGL on DRM) + struct gbm_surface *gbmSurface; // surface of GBM + struct gbm_bo *prevBO; // previous used GBM buffer object (during frame swapping) + uint32_t prevFB; // previous used GBM framebufer (during frame swapping) +#endif + EGLDisplay device; // Native display device (physical screen connection) + EGLSurface surface; // Surface to draw on, framebuffers (connected to context) + EGLContext context; // Graphic context, mode in which drawing can be done + EGLConfig config; // Graphic config +#endif + const char *title; // Window text title const pointer + unsigned int flags; // Configuration flags (bit based), keeps window state + bool ready; // Check if window has been initialized successfully + bool fullscreen; // Check if fullscreen mode is enabled + bool shouldClose; // Check if window set for closing + bool resizedLastFrame; // Check if window has been resized last frame + + Point position; // Window position on screen (required on fullscreen toggle) + Size display; // Display width and height (monitor, device-screen, LCD, ...) + Size screen; // Screen width and height (used render area) + Size currentFbo; // Current render width and height, it could change on BeginTextureMode() + Size render; // Framebuffer width and height (render area, including black bars if required) + Point renderOffset; // Offset from render area (must be divided by 2) + Matrix screenScale; // Matrix to scale screen (framebuffer rendering) + + char **dropFilesPath; // Store dropped files paths as strings + int dropFilesCount; // Count dropped files strings + + } Window; +#if defined(PLATFORM_ANDROID) + struct { + bool appEnabled; // Flag to detect if app is active ** = true + struct android_app *app; // Android activity + struct android_poll_source *source; // Android events polling source + const char *internalDataPath; // Android internal data path to write data (/data/data//files) + bool contextRebindRequired; // Used to know context rebind required + } Android; +#endif +#if defined(PLATFORM_UWP) + struct { + const char *internalDataPath; // UWP App data path + } UWP; +#endif + struct { +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event" +#endif + struct { + int exitKey; // Default exit key + char currentKeyState[512]; // Registers current frame key state + char previousKeyState[512]; // Registers previous frame key state + + int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue + int keyPressedQueueCount; // Input keys queue count + + int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue + int charPressedQueueCount; // Input characters queue count + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + int defaultMode; // Default keyboard mode + struct termios defaultSettings; // Default keyboard settings + int fd; // File descriptor for the evdev keyboard +#endif + } Keyboard; + struct { + Vector2 position; // Mouse position on screen + Vector2 offset; // Mouse offset + Vector2 scale; // Mouse scaling + + int cursor; // Tracks current mouse cursor + bool cursorHidden; // Track if cursor is hidden + bool cursorOnScreen; // Tracks if cursor is inside client area + + char currentButtonState[3]; // Registers current mouse button state + char previousButtonState[3]; // Registers previous mouse button state + float currentWheelMove; // Registers current mouse wheel variation + float previousWheelMove; // Registers previous mouse wheel variation +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + char currentButtonStateEvdev[3]; // Holds the new mouse state for the next polling event to grab (Can't be written directly due to multithreading, app could miss the update) +#endif + } Mouse; + struct { + Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen + char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state + char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state + } Touch; + struct { + int lastButtonPressed; // Register last gamepad button pressed + int axisCount; // Register number of available gamepad axis + bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready + float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state + char currentState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state + char previousState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + pthread_t threadId; // Gamepad reading thread id + int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor + char name[64]; // Gamepad name holder +#endif + } Gamepad; + } Input; + struct { + double current; // Current time measure + double previous; // Previous time measure + double update; // Time measure for frame update + double draw; // Time measure for frame draw + double frame; // Time measure for one frame + double target; // Desired time for one frame, if 0 not applied +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + unsigned long long base; // Base time measure for hi-res timer +#endif + } Time; +} CoreData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static CoreData CORE = { 0 }; // Global CORE state context + +static char **dirFilesPath = NULL; // Store directory files paths as strings +static int dirFilesCount = 0; // Count directory files strings + +#if defined(SUPPORT_SCREEN_CAPTURE) +static int screenshotCounter = 0; // Screenshots counter +#endif + +#if defined(SUPPORT_GIF_RECORDING) +static int gifFramesCounter = 0; // GIF frames counter +static bool gifRecording = false; // GIF recording state +static MsfGifState gifState = { 0 }; // MSGIF context state +#endif +//----------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by core) +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_DEFAULT_FONT) +extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() +extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static bool InitGraphicsDevice(int width, int height); // Initialize graphics device +static void SetupFramebuffer(int width, int height); // Setup main framebuffer +static void SetupViewport(int width, int height); // Set viewport for a provided width and height +static void SwapBuffers(void); // Copy back buffer to front buffer + +static void InitTimer(void); // Initialize timer +static void Wait(float ms); // Wait for some milliseconds (stop program execution) + +static void PollInputEvents(void); // Register user events + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) +static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error +// Window callbacks events +static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +#if !defined(PLATFORM_WEB) +static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized +#endif +static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window +// Input callbacks events +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed +static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed +static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel +static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area +#endif + +#if defined(PLATFORM_ANDROID) +static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs +#endif + +#if defined(PLATFORM_WEB) +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(SUPPORT_SSH_KEYBOARD_RPI) +static void InitKeyboard(void); // Init raw keyboard system (standard input reading) +static void ProcessKeyboard(void); // Process keyboard events +static void RestoreKeyboard(void); // Restore keyboard system +#else +static void InitTerminal(void); // Init terminal (block echo and signal short cuts) +static void RestoreTerminal(void); // Restore terminal +#endif + +static void InitEvdevInput(void); // Evdev inputs initialization +static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate +static void PollKeyboardEvents(void); // Process evdev keyboard events. +static void *EventThread(void *arg); // Input device events reading thread + +static void InitGamepad(void); // Init raw gamepad input +static void *GamepadThread(void *arg); // Mouse reading thread + +#if defined(PLATFORM_DRM) +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list +#endif + +#endif // PLATFORM_RPI || PLATFORM_DRM + +#if defined(_WIN32) + // NOTE: We include Sleep() function signature here to avoid windows.h inclusion (kernel32 lib) + void __stdcall Sleep(unsigned long msTimeout); // Required for Wait() +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Window and OpenGL Context Functions +//---------------------------------------------------------------------------------- + +#if defined(PLATFORM_ANDROID) +// To allow easier porting to android, we allow the user to define a +// main function which we call from android_main, defined by ourselves +extern int main(int argc, char *argv[]); + +void android_main(struct android_app *app) +{ + char arg0[] = "raylib"; // NOTE: argv[] are mutable + CORE.Android.app = app; + + // TODO: Should we maybe report != 0 return codes somewhere? + (void)main(1, (char *[]) { arg0, NULL }); +} + +// TODO: Add this to header (if apps really need it) +struct android_app *GetAndroidApp(void) +{ + return CORE.Android.app; +} +#endif +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && !defined(SUPPORT_SSH_KEYBOARD_RPI) +// Init terminal (block echo and signal short cuts) +static void InitTerminal(void) +{ + TRACELOG(LOG_INFO, "RPI: Reconfiguring terminal..."); + + // Save terminal keyboard settings and reconfigure terminal with new settings + struct termios keyboardNewSettings; + tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings + keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; + + // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo + // NOTE: ISIG controls if ^C and ^Z generate break signals or not + keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); + keyboardNewSettings.c_cc[VMIN] = 1; + keyboardNewSettings.c_cc[VTIME] = 0; + + // Set new keyboard settings (change occurs immediately) + tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); + + // Save old keyboard mode to restore it at the end + if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0) + { + // NOTE: It could mean we are using a remote keyboard through ssh or from the desktop + TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (not a local terminal)"); + } + else ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); + + // Register terminal restore when program finishes + atexit(RestoreTerminal); +} +// Restore terminal +static void RestoreTerminal(void) +{ + TRACELOG(LOG_INFO, "RPI: Restoring terminal..."); + + // Reset to default keyboard settings + tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); + + // Reconfigure keyboard to default mode + ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); +} +#endif +// Initialize window and OpenGL context +// NOTE: data parameter could be used to pass any kind of required data to the initialization +void InitWindow(int width, int height, const char *title) +{ +#if defined(PLATFORM_UWP) + if (!UWPIsConfigured()) + { + TRACELOG(LOG_ERROR, "UWP Functions have not been set yet, please set these before initializing raylib!"); + return; + } +#endif + + TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); + + if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; + + // Initialize required global values different than 0 + CORE.Input.Keyboard.exitKey = KEY_ESCAPE; + CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; + CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; + CORE.Input.Gamepad.lastButtonPressed = -1; + +#if defined(PLATFORM_UWP) + // The axis count is 6 (2 thumbsticks and left and right trigger) + CORE.Input.Gamepad.axisCount = 6; +#endif + +#if defined(PLATFORM_ANDROID) + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + + // Input data is android app pointer + CORE.Android.internalDataPath = CORE.Android.app->activity->internalDataPath; + + // Set desired windows flags before initializing anything + ANativeActivity_setWindowFlags(CORE.Android.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER + + int orientation = AConfiguration_getOrientation(CORE.Android.app->config); + + if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait"); + else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape"); + + // TODO: Automatic orientation doesn't seem to work + if (width <= height) + { + AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_PORT); + TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait"); + } + else + { + AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_LAND); + TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape"); + } + + //AConfiguration_getDensity(CORE.Android.app->config); + //AConfiguration_getKeyboard(CORE.Android.app->config); + //AConfiguration_getScreenSize(CORE.Android.app->config); + //AConfiguration_getScreenLong(CORE.Android.app->config); + + CORE.Android.app->onAppCmd = AndroidCommandCallback; + CORE.Android.app->onInputEvent = AndroidInputCallback; + + InitAssetManager(CORE.Android.app->activity->assetManager, CORE.Android.app->activity->internalDataPath); + + TRACELOG(LOG_INFO, "ANDROID: App initialized successfully"); + + // Android ALooper_pollAll() variables + int pollResult = 0; + int pollEvents = 0; + + // Wait for window to be initialized (display and context) + while (!CORE.Window.ready) + { + // Process events loop + while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) + { + // Process this event + if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); + + // NOTE: Never close window, native activity is controlled by the system! + //if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true; + } + } +#endif +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) || defined(PLATFORM_DRM) + // Init graphics device (display device and OpenGL context) + // NOTE: returns true if window and graphic device has been initialized successfully + CORE.Window.ready = InitGraphicsDevice(width, height); + + if (!CORE.Window.ready) return; + + // Init hi-res timer + InitTimer(); + +#if defined(SUPPORT_DEFAULT_FONT) + // Load default font + // NOTE: External functions (defined in module: text) + LoadFontDefault(); + Rectangle rec = GetFontDefault().recs[95]; + // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); +#else + // Set default internal texture (1px white) and rectangle to be used for shapes drawing + SetShapesTexture(rlGetTextureDefault(), (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); +#endif +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // Set default font texture filter for HighDPI (blurry) + SetTextureFilter(GetFontDefault().texture, TEXTURE_FILTER_BILINEAR); + } +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + // Init raw input system + InitEvdevInput(); // Evdev inputs initialization + InitGamepad(); // Gamepad init +#if defined(SUPPORT_SSH_KEYBOARD_RPI) + InitKeyboard(); // Keyboard init +#else + InitTerminal(); // Terminal init +#endif +#endif + +#if defined(PLATFORM_WEB) + // Detect fullscreen change events + //emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); + //emscripten_set_resize_callback("#canvas", NULL, 1, EmscriptenResizeCallback); + + // Support keyboard events + //emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); + //emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); + + // Support touch events + emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + + // Support gamepad events (not provided by GLFW3 on emscripten) + emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); + emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); +#endif + + CORE.Input.Mouse.position.x = (float)CORE.Window.screen.width/2.0f; + CORE.Input.Mouse.position.y = (float)CORE.Window.screen.height/2.0f; +#endif // PLATFORM_ANDROID +} + +// Close window and unload OpenGL context +void CloseWindow(void) +{ +#if defined(SUPPORT_GIF_RECORDING) + if (gifRecording) + { + MsfGifResult result = msf_gif_end(&gifState); + msf_gif_free(result); + gifRecording = false; + } +#endif + +#if defined(SUPPORT_DEFAULT_FONT) + UnloadFontDefault(); +#endif + + rlglClose(); // De-init rlgl + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + glfwDestroyWindow(CORE.Window.handle); + glfwTerminate(); +#endif + +#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) + timeEndPeriod(1); // Restore time period +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) +#if defined(PLATFORM_DRM) + if (CORE.Window.prevFB) + { + drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); + CORE.Window.prevFB = 0; + } + + if (CORE.Window.prevBO) + { + gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); + CORE.Window.prevBO = NULL; + } + + if (CORE.Window.gbmSurface) + { + gbm_surface_destroy(CORE.Window.gbmSurface); + CORE.Window.gbmSurface = NULL; + } + + if (CORE.Window.gbmDevice) + { + gbm_device_destroy(CORE.Window.gbmDevice); + CORE.Window.gbmDevice = NULL; + } + + if (CORE.Window.crtc) + { + if (CORE.Window.connector) + { + drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, CORE.Window.crtc->buffer_id, + CORE.Window.crtc->x, CORE.Window.crtc->y, &CORE.Window.connector->connector_id, 1, &CORE.Window.crtc->mode); + drmModeFreeConnector(CORE.Window.connector); + CORE.Window.connector = NULL; + } + + drmModeFreeCrtc(CORE.Window.crtc); + CORE.Window.crtc = NULL; + } + + if (CORE.Window.fd != -1) + { + close(CORE.Window.fd); + CORE.Window.fd = -1; + } +#endif + + // Close surface, context and display + if (CORE.Window.device != EGL_NO_DISPLAY) + { +#if !defined(PLATFORM_DRM) + eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +#endif + if (CORE.Window.surface != EGL_NO_SURFACE) + { + eglDestroySurface(CORE.Window.device, CORE.Window.surface); + CORE.Window.surface = EGL_NO_SURFACE; + } + + if (CORE.Window.context != EGL_NO_CONTEXT) + { + eglDestroyContext(CORE.Window.device, CORE.Window.context); + CORE.Window.context = EGL_NO_CONTEXT; + } + + eglTerminate(CORE.Window.device); + CORE.Window.device = EGL_NO_DISPLAY; + } +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + // Wait for mouse and gamepad threads to finish before closing + // NOTE: Those threads should already have finished at this point + // because they are controlled by CORE.Window.shouldClose variable + + CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called + + // Close the evdev keyboard + if (CORE.Input.Keyboard.fd != -1) + { + close(CORE.Input.Keyboard.fd); + CORE.Input.Keyboard.fd = -1; + } + + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].threadId) + { + pthread_join(CORE.Input.eventWorker[i].threadId, NULL); + } + } + + + if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL); +#endif + + TRACELOG(LOG_INFO, "Window closed successfully"); +} + +// Check if KEY_ESCAPE pressed or Close icon pressed +bool WindowShouldClose(void) +{ +#if defined(PLATFORM_WEB) + // Emterpreter-Async required to run sync code + // https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code + // By default, this function is never called on a web-ready raylib example because we encapsulate + // frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously + // but now emscripten allows sync code to be executed in an interpreted way, using emterpreter! + emscripten_sleep(16); + return false; +#endif + +#if defined(PLATFORM_DESKTOP) + if (CORE.Window.ready) + { + // While window minimized, stop loop execution + while (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN)) glfwWaitEvents(); + + CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle); + + // Reset close status for next frame + glfwSetWindowShouldClose(CORE.Window.handle, GLFW_FALSE); + + return CORE.Window.shouldClose; + } + else return true; +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +#endif +} + +// Check if window has been initialized successfully +bool IsWindowReady(void) +{ + return CORE.Window.ready; +} + +// Check if window is currently fullscreen +bool IsWindowFullscreen(void) +{ + return CORE.Window.fullscreen; +} + +// Check if window is currently hidden +bool IsWindowHidden(void) +{ +#if defined(PLATFORM_DESKTOP) + return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0); +#endif + return false; +} + +// Check if window has been minimized +bool IsWindowMinimized(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0); +#else + return false; +#endif +} + +// Check if window has been maximized (only PLATFORM_DESKTOP) +bool IsWindowMaximized(void) +{ +#if defined(PLATFORM_DESKTOP) + return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0); +#else + return false; +#endif +} + +// Check if window has the focus +bool IsWindowFocused(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); // TODO! +#else + return true; +#endif +} + +// Check if window has been resizedLastFrame +bool IsWindowResized(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return CORE.Window.resizedLastFrame; +#else + return false; +#endif +} + +// Check if one specific window flag is enabled +bool IsWindowState(unsigned int flag) +{ + return ((CORE.Window.flags & flag) > 0); +} + +// Toggle fullscreen mode (only PLATFORM_DESKTOP) +void ToggleFullscreen(void) +{ +#if defined(PLATFORM_DESKTOP) + // NOTE: glfwSetWindowMonitor() doesn't work properly (bugs) + if (!CORE.Window.fullscreen) + { + // Store previous window position (in case we exit fullscreen) + glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y); + + int monitorCount = 0; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + + int monitorIndex = GetCurrentMonitor(); + + // Use current monitor, so we correctly get the display the window is on + GLFWmonitor* monitor = monitorIndex < monitorCount ? monitors[monitorIndex] : NULL; + + if (!monitor) + { + TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); + + CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + return; + } + + CORE.Window.fullscreen = true; // Toggle fullscreen flag + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + else + { + CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration + if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); +#endif +#if defined(PLATFORM_WEB) + EM_ASM + ( + // This strategy works well while using raylib minimal web shell for emscripten, + // it re-scales the canvas to fullscreen using monitor resolution, for tools this + // is a good strategy but maybe games prefer to keep current canvas resolution and + // display it in fullscreen, adjusting monitor resolution if possible + if (document.fullscreenElement) document.exitFullscreen(); + else Module.requestFullscreen(false, true); + ); + +/* + if (!CORE.Window.fullscreen) + { + // Option 1: Request fullscreen for the canvas element + // This option does not seem to work at all + //emscripten_request_fullscreen("#canvas", false); + + // Option 2: Request fullscreen for the canvas element with strategy + // This option does not seem to work at all + // Ref: https://github.com/emscripten-core/emscripten/issues/5124 + // EmscriptenFullscreenStrategy strategy = { + // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + // .canvasResizedCallback = EmscriptenWindowResizedCallback, + // .canvasResizedCallbackUserData = NULL + // }; + //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); + + // Option 3: Request fullscreen for the canvas element with strategy + // It works as expected but only inside the browser (client area) + EmscriptenFullscreenStrategy strategy = { + .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + .canvasResizedCallback = EmscriptenWindowResizedCallback, + .canvasResizedCallbackUserData = NULL + }; + emscripten_enter_soft_fullscreen("#canvas", &strategy); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); + } + else + { + //emscripten_exit_fullscreen(); + emscripten_exit_soft_fullscreen(); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); + } +*/ + + CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag + CORE.Window.flags ^= FLAG_FULLSCREEN_MODE; +#endif +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode"); +#endif +} + +// Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +void MaximizeWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + glfwMaximizeWindow(CORE.Window.handle); + CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + } +#endif +} + +// Set window state: minimized (only PLATFORM_DESKTOP) +void MinimizeWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + // NOTE: Following function launches callback that sets appropiate flag! + glfwIconifyWindow(CORE.Window.handle); +#endif +} + +// Set window state: not minimized/maximized (only PLATFORM_DESKTOP) +void RestoreWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + // Restores the specified window if it was previously iconified (minimized) or maximized + glfwRestoreWindow(CORE.Window.handle); + CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + } +#endif +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ +#if defined(PLATFORM_DESKTOP) + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0)) + { + glfwSwapInterval(1); + CORE.Window.flags |= FLAG_VSYNC_HINT; + } + + // State change: FLAG_FULLSCREEN_MODE + if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE)) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_TRUE); + CORE.Window.flags |= FLAG_WINDOW_RESIZABLE; + } + + // State change: FLAG_WINDOW_UNDECORATED + if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE); + CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; + } + + // State change: FLAG_WINDOW_HIDDEN + if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + { + glfwHideWindow(CORE.Window.handle); + CORE.Window.flags |= FLAG_WINDOW_HIDDEN; + } + + // State change: FLAG_WINDOW_MINIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) != (flags & FLAG_WINDOW_MINIMIZED)) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + { + //GLFW_ICONIFIED + MinimizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + { + //GLFW_MAXIMIZED + MaximizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) != (flags & FLAG_WINDOW_UNFOCUSED)) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE); + CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; + } + + // State change: FLAG_WINDOW_TOPMOST + if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE); + CORE.Window.flags |= FLAG_WINDOW_TOPMOST; + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) != (flags & FLAG_WINDOW_ALWAYS_RUN)) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + { + CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) != (flags & FLAG_WINDOW_TRANSPARENT)) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) != (flags & FLAG_WINDOW_HIGHDPI)) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); + } + + // State change: FLAG_MSAA_4X_HINT + if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if (((CORE.Window.flags & FLAG_INTERLACED_HINT) != (flags & FLAG_INTERLACED_HINT)) && ((flags & FLAG_INTERLACED_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); + } +#endif +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ +#if defined(PLATFORM_DESKTOP) + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0)) + { + glfwSwapInterval(0); + CORE.Window.flags &= ~FLAG_VSYNC_HINT; + } + + // State change: FLAG_FULLSCREEN_MODE + if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_FALSE); + CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE; + } + + // State change: FLAG_WINDOW_UNDECORATED + if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE); + CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; + } + + // State change: FLAG_WINDOW_HIDDEN + if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + { + glfwShowWindow(CORE.Window.handle); + CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; + } + + // State change: FLAG_WINDOW_MINIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE); + CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; + } + + // State change: FLAG_WINDOW_TOPMOST + if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE); + CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST; + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + { + CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); + } + + // State change: FLAG_MSAA_4X_HINT + if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); + } +#endif +} + +// Set icon for window (only PLATFORM_DESKTOP) +// NOTE: Image must be in RGBA format, 8bit per channel +void SetWindowIcon(Image image) +{ +#if defined(PLATFORM_DESKTOP) + if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + GLFWimage icon[1] = { 0 }; + + icon[0].width = image.width; + icon[0].height = image.height; + icon[0].pixels = (unsigned char *)image.data; + + // NOTE 1: We only support one image icon + // NOTE 2: The specified image data is copied before this function returns + glfwSetWindowIcon(CORE.Window.handle, 1, icon); + } + else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format"); +#endif +} + +// Set title for window (only PLATFORM_DESKTOP) +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; +#if defined(PLATFORM_DESKTOP) + glfwSetWindowTitle(CORE.Window.handle, title); +#endif +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetWindowPos(CORE.Window.handle, x, y); +#endif +} + +// Set monitor for the current window (fullscreen mode) +void SetWindowMonitor(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); + + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); + glfwSetWindowMonitor(CORE.Window.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ +#if defined(PLATFORM_DESKTOP) + const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + glfwSetWindowSizeLimits(CORE.Window.handle, width, height, mode->width, mode->height); +#endif +} + +// Set window dimensions +// TODO: Issues on HighDPI scaling +void SetWindowSize(int width, int height) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetWindowSize(CORE.Window.handle, width, height); +#endif +#if defined(PLATFORM_WEB) + //emscripten_set_canvas_size(width, height); // DEPRECATED! + + // TODO: Below functions should be used to replace previous one but + // they do not seem to work properly + //emscripten_set_canvas_element_size("canvas", width, height); + //emscripten_set_element_css_size("canvas", width, height); +#endif +} + +// Get current screen width +int GetScreenWidth(void) +{ + return CORE.Window.currentFbo.width; +} + +// Get current screen height +int GetScreenHeight(void) +{ + return CORE.Window.currentFbo.height; +} + +// Get native window handle +void *GetWindowHandle(void) +{ +#if defined(PLATFORM_DESKTOP) && defined(_WIN32) + // NOTE: Returned handle is: void *HWND (windows.h) + return glfwGetWin32Window(CORE.Window.handle); +#endif +#if defined(__linux__) + // NOTE: Returned handle is: unsigned long Window (X.h) + // typedef unsigned long XID; + // typedef XID Window; + //unsigned long id = (unsigned long)glfwGetX11Window(window); + return NULL; // TODO: Find a way to return value... cast to void *? +#endif +#if defined(__APPLE__) + // NOTE: Returned handle is: (objc_object *) + return NULL; // TODO: return (void *)glfwGetCocoaWindow(window); +#endif + + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + glfwGetMonitors(&monitorCount); + return monitorCount; +#else + return 1; +#endif +} + +// Get number of monitors +int GetCurrentMonitor(void) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + GLFWmonitor* monitor = NULL; + + if (monitorCount == 1) // easy out + return 0; + + if (IsWindowFullscreen()) + { + monitor = glfwGetWindowMonitor(CORE.Window.handle); + for (int i = 0; i < monitorCount; i++) + { + if (monitors[i] == monitor) + return i; + } + return 0; + } + else + { + int x = 0; + int y = 0; + + glfwGetWindowPos(CORE.Window.handle, &x, &y); + + for (int i = 0; i < monitorCount; i++) + { + int mx = 0; + int my = 0; + + int width = 0; + int height = 0; + + monitor = monitors[i]; + glfwGetMonitorWorkarea(monitor, &mx, &my, &width, &height); + if (x >= mx && x <= (mx + width) && y >= my && y <= (my + height)) + return i; + } + } + return 0; +#else + return 0; +#endif +} + +// Get selected monitor width +Vector2 GetMonitorPosition(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int x, y; + glfwGetMonitorPos(monitors[monitor], &x, &y); + + return (Vector2){ (float)x, (float)y }; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (max available by monitor) +int GetMonitorWidth(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + + // We return the maximum resolution available, the last one in the modes array + if (count > 0) return modes[count - 1].width; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +// Get selected monitor width (max available by monitor) +int GetMonitorHeight(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + + // We return the maximum resolution available, the last one in the modes array + if (count > 0) return modes[count - 1].height; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int physicalWidth; + glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL); + return physicalWidth; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +// Get primary monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int physicalHeight; + glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight); + return physicalHeight; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +int GetMonitorRefreshRate(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + const GLFWvidmode *vidmode = glfwGetVideoMode(monitors[monitor]); + return vidmode->refreshRate; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif +#if defined(PLATFORM_DRM) + if ((CORE.Window.connector) && (CORE.Window.modeIndex >= 0)) + { + return CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh; + } +#endif + return 0; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + int x = 0; + int y = 0; +#if defined(PLATFORM_DESKTOP) + glfwGetWindowPos(CORE.Window.handle, &x, &y); +#endif + return (Vector2){ (float)x, (float)y }; +} + +// Get window scale DPI factor +Vector2 GetWindowScaleDPI(void) +{ + Vector2 scale = { 1.0f, 1.0f }; + +#if defined(PLATFORM_DESKTOP) + float xdpi = 1.0; + float ydpi = 1.0; + Vector2 windowPos = GetWindowPosition(); + + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + // Check window monitor + for (int i = 0; i < monitorCount; i++) + { + glfwGetMonitorContentScale(monitors[i], &xdpi, &ydpi); + + int xpos, ypos, width, height; + glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height); + + if ((windowPos.x >= xpos) && (windowPos.x < xpos + width) && + (windowPos.y >= ypos) && (windowPos.y < ypos + height)) + { + scale.x = xdpi; + scale.y = ydpi; + break; + } + } +#endif + + return scale; +} + +// Get the human-readable, UTF-8 encoded name of the primary monitor +const char *GetMonitorName(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + return glfwGetMonitorName(monitors[monitor]); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return ""; +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ +#if defined(PLATFORM_DESKTOP) + return glfwGetClipboardString(CORE.Window.handle); +#else + return NULL; +#endif +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetClipboardString(CORE.Window.handle, text); +#endif +} + +// Show mouse cursor +void ShowCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseShowFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = false; +} + +// Hides mouse cursor +void HideCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseHideFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = true; +} + +// Check if cursor is not visible +bool IsCursorHidden(void) +{ + return CORE.Input.Mouse.cursorHidden; +} + +// Enables cursor (unlock cursor) +void EnableCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +#endif +#if defined(PLATFORM_WEB) + emscripten_exit_pointerlock(); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseUnlockFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = false; +} + +// Disables cursor (lock cursor) +void DisableCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +#endif +#if defined(PLATFORM_WEB) + emscripten_request_pointerlock("#canvas", 1); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseLockFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = true; +} + +// Check if cursor is on the current screen. +bool IsCursorOnScreen(void) +{ + return CORE.Input.Mouse.cursorOnScreen; +} + +// Set background color (framebuffer clear color) +void ClearBackground(Color color) +{ + rlClearColor(color.r, color.g, color.b, color.a); // Set clear color + rlClearScreenBuffers(); // Clear current framebuffers +} + +// Setup canvas (framebuffer) to start drawing +void BeginDrawing(void) +{ + CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer() + CORE.Time.update = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + rlLoadIdentity(); // Reset current matrix (modelview) + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling + + //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 + // NOTE: Not required with OpenGL 3.3+ +} + +// End canvas drawing and swap buffers (double buffering) +void EndDrawing(void) +{ +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_MOUSE_CURSOR_NATIVE) + // On native mode we have no system mouse cursor, so, + // we draw a small rectangle for user reference + if (!CORE.Input.Mouse.cursorHidden) + { + DrawRectangle(CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y, 3, 3, MAROON); + } +#endif + + rlDrawRenderBatchActive(); // Update and draw internal render batch + +#if defined(SUPPORT_GIF_RECORDING) + #define GIF_RECORD_FRAMERATE 10 + + if (gifRecording) + { + gifFramesCounter++; + + // NOTE: We record one gif frame every 10 game frames + if ((gifFramesCounter%GIF_RECORD_FRAMERATE) == 0) + { + // Get image data for the current frame (from backbuffer) + // NOTE: This process is quite slow... :( + unsigned char *screenData = rlReadScreenPixels(CORE.Window.screen.width, CORE.Window.screen.height); + msf_gif_frame(&gifState, screenData, 10, 16, CORE.Window.screen.width*4); + + RL_FREE(screenData); // Free image data + } + + if (((gifFramesCounter/15)%2) == 1) + { + DrawCircle(30, CORE.Window.screen.height - 20, 10, RED); + DrawText("RECORDING", 50, CORE.Window.screen.height - 25, 10, MAROON); + } + + rlDrawRenderBatchActive(); // Update and draw internal render batch + } +#endif + + SwapBuffers(); // Copy back buffer to front buffer + + // Frame time control system + CORE.Time.current = GetTime(); + CORE.Time.draw = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + CORE.Time.frame = CORE.Time.update + CORE.Time.draw; + + // Wait for some milliseconds... + if (CORE.Time.frame < CORE.Time.target) + { + Wait((float)(CORE.Time.target - CORE.Time.frame)*1000.0f); + + CORE.Time.current = GetTime(); + double waitTime = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + CORE.Time.frame += waitTime; // Total frame time: update + draw + wait + } + + PollInputEvents(); // Poll user events +} + +// Initialize 2D mode with custom camera (2D) +void BeginMode2D(Camera2D camera) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlLoadIdentity(); // Reset current matrix (modelview) + + // Apply 2d camera transformation to modelview + rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); + + // Apply screen scaling if required + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); +} + +// Ends 2D mode with custom camera +void EndMode2D(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlLoadIdentity(); // Reset current matrix (modelview) + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required +} + +// Initializes 3D mode with custom camera (3D) +void BeginMode3D(Camera3D camera) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection + rlLoadIdentity(); // Reset current matrix (projection) + + float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; + + // NOTE: zNear and zFar values are important when computing depth buffer values + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Setup perspective projection + double top = RL_CULL_DISTANCE_NEAR*tan(camera.fovy*0.5*DEG2RAD); + double right = top*aspect; + + rlFrustum(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + // Setup orthographic projection + double top = camera.fovy/2.0; + double right = top*aspect; + + rlOrtho(-right, right, -top,top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + // Setup Camera view + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) + + rlEnableDepthTest(); // Enable DEPTH_TEST for 3D +} + +// Ends 3D mode and returns to default 2D orthographic mode +void EndMode3D(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPopMatrix(); // Restore previous matrix (projection) from matrix stack + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required + + rlDisableDepthTest(); // Disable DEPTH_TEST for 2D +} + +// Initializes render texture for drawing +void BeginTextureMode(RenderTexture2D target) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlEnableFramebuffer(target.id); // Enable render target + + // Set viewport to framebuffer size + rlViewport(0, 0, target.texture.width, target.texture.height); + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlLoadIdentity(); // Reset current matrix (projection) + + // Set orthographic projection to current framebuffer size + // NOTE: Configured top-left corner as (0, 0) + rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) + + // Setup current width/height for proper aspect ratio + // calculation when using BeginMode3D() + CORE.Window.currentFbo.width = target.texture.width; + CORE.Window.currentFbo.height = target.texture.height; +} + +// Ends drawing to render texture +void EndTextureMode(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlDisableFramebuffer(); // Disable render target (fbo) + + // Set viewport to default framebuffer size + SetupViewport(CORE.Window.render.width, CORE.Window.render.height); + + // Reset current fbo to screen size + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; +} + +// Begin custom shader mode +void BeginShaderMode(Shader shader) +{ + rlSetShader(shader); +} + +// End custom shader mode (returns to default shader) +void EndShaderMode(void) +{ + rlSetShader(rlGetShaderDefault()); +} + +// Begin blending mode (alpha, additive, multiplied) +// NOTE: Only 3 blending modes supported, default blend mode is alpha +void BeginBlendMode(int mode) +{ + rlSetBlendMode(mode); +} + +// End blending mode (reset to default: alpha blending) +void EndBlendMode(void) +{ + rlSetBlendMode(BLEND_ALPHA); +} + +// Begin scissor mode (define screen area for following drawing) +// NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left +void BeginScissorMode(int x, int y, int width, int height) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlEnableScissorTest(); + rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); +} + +// End scissor mode +void EndScissorMode(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + rlDisableScissorTest(); +} + +// Begin VR drawing configuration +void BeginVrStereoMode(VrStereoConfig config) +{ + rlEnableStereoRender(); + + // Set stereo render matrices + rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]); + rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]); +} + +// End VR drawing process (and desktop mirror) +void EndVrStereoMode(void) +{ + rlDisableStereoRender(); +} + +// Load VR stereo config for VR simulator device parameters +VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) +{ + VrStereoConfig config = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Compute aspect ratio + float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution; + + // Compute lens parameters + float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize; + config.leftLensCenter[0] = 0.25f + lensShift; + config.leftLensCenter[1] = 0.5f; + config.rightLensCenter[0] = 0.75f - lensShift; + config.rightLensCenter[1] = 0.5f; + config.leftScreenCenter[0] = 0.25f; + config.leftScreenCenter[1] = 0.5f; + config.rightScreenCenter[0] = 0.75f; + config.rightScreenCenter[1] = 0.5f; + + // Compute distortion scale parameters + // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] + float lensRadius = fabsf(-1.0f - 4.0f*lensShift); + float lensRadiusSq = lensRadius*lensRadius; + float distortionScale = device.lensDistortionValues[0] + + device.lensDistortionValues[1]*lensRadiusSq + + device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + + device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; + + float normScreenWidth = 0.5f; + float normScreenHeight = 1.0f; + config.scaleIn[0] = 2.0f/normScreenWidth; + config.scaleIn[1] = 2.0f/normScreenHeight/aspect; + config.scale[0] = normScreenWidth*0.5f/distortionScale; + config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale; + + // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) + // ...but with lens distortion it is increased (see Oculus SDK Documentation) + //float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? + float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); + + // Compute camera projection matrices + float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] + Matrix proj = MatrixPerspective(fovy, aspect, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + + config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); + config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); + + // Compute camera transformation matrices + // NOTE: Camera movement might seem more natural if we model the head. + // Our axis of rotation is the base of our head, so we might want to add + // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. + config.viewOffset[0] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + config.viewOffset[1] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + + // Compute eyes Viewports + /* + config.eyeViewportRight[0] = 0; + config.eyeViewportRight[1] = 0; + config.eyeViewportRight[2] = device.hResolution/2; + config.eyeViewportRight[3] = device.vResolution; + + config.eyeViewportLeft[0] = device.hResolution/2; + config.eyeViewportLeft[1] = 0; + config.eyeViewportLeft[2] = device.hResolution/2; + config.eyeViewportLeft[3] = device.vResolution; + */ +#else + TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); +#endif + + return config; +} + +// Unload VR stereo config properties +void UnloadVrStereoConfig(VrStereoConfig config) +{ + //... +} + +// Load shader from files and bind default locations +// NOTE: If shader string is NULL, using default vertex/fragment shaders +Shader LoadShader(const char *vsFileName, const char *fsFileName) +{ + Shader shader = { 0 }; + shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + + char *vShaderStr = NULL; + char *fShaderStr = NULL; + + if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); + if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); + + shader.id = rlLoadShaderCode(vShaderStr, fShaderStr); + + if (vShaderStr != NULL) RL_FREE(vShaderStr); + if (fShaderStr != NULL) RL_FREE(fShaderStr); + + // After shader loading, we TRY to set default location names + if (shader.id > 0) + { + // Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 + + // NOTE: If any location is not found, loc point becomes -1 + + // Get handles to GLSL input attibute locations + shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Get handles to GLSL uniform locations (vertex shader) + shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, "mvp"); + shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, "view"); + shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, "projection"); + shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, "matNormal"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, "colDiffuse"); + shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, "texture0"); + shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, "texture1"); + shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, "texture2"); + } + + return shader; +} + +// Load shader from code strings and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) +{ + Shader shader = { 0 }; + shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + shader.id = rlLoadShaderCode(vsCode, fsCode); + + // After shader loading, we TRY to set default location names + if (shader.id > 0) + { + // Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 + + // NOTE: If any location is not found, loc point becomes -1 + + // Get handles to GLSL input attibute locations + shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Get handles to GLSL uniform locations (vertex shader) + shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, "mvp"); + shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, "projection"); + shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, "view"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, "colDiffuse"); + shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, "texture0"); + shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, "texture1"); + shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, "texture2"); + } + + return shader; +} + +// Unload shader from GPU memory (VRAM) +void UnloadShader(Shader shader) +{ + if (shader.id != rlGetShaderDefault().id) + { + rlUnloadShaderProgram(shader.id); + RL_FREE(shader.locs); + } +} + +// Get shader uniform location +int GetShaderLocation(Shader shader, const char *uniformName) +{ + return rlGetLocationUniform(shader.id, uniformName); +} + +// Get shader attribute location +int GetShaderLocationAttrib(Shader shader, const char *attribName) +{ + return rlGetLocationAttrib(shader.id, attribName); +} + +// Set shader uniform value +void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType) +{ + SetShaderValueV(shader, locIndex, value, uniformType, 1); +} + +// Set shader uniform value vector +void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count) +{ + rlEnableShader(shader.id); + rlSetUniform(locIndex, value, uniformType, count); + //rlDisableShader(); // Avoid reseting current shader program, in case other uniforms are set +} + +// Set shader uniform value (matrix 4x4) +void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat) +{ + rlEnableShader(shader.id); + rlSetUniformMatrix(locIndex, mat); + //rlDisableShader(); +} + +// Set shader uniform value for texture +void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture) +{ + rlEnableShader(shader.id); + rlSetUniformSampler(locIndex, texture.id); + //rlDisableShader(); +} + +// Returns a ray trace from mouse position +Ray GetMouseRay(Vector2 mouse, Camera camera) +{ + Ray ray; + + // Calculate normalized device coordinates + // NOTE: y value is negative + float x = (2.0f*mouse.x)/(float)GetScreenWidth() - 1.0f; + float y = 1.0f - (2.0f*mouse.y)/(float)GetScreenHeight(); + float z = 1.0f; + + // Store values in a vector + Vector3 deviceCoords = { x, y, z }; + + // Calculate view matrix from camera look at + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + Matrix matProj = MatrixIdentity(); + + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + double top = camera.fovy/2.0; + double right = top*aspect; + + // Calculate projection matrix from orthographic + matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); + } + + // Unproject far/near points + Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); + Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); + + // Unproject the mouse cursor in the near plane. + // We need this as the source position because orthographic projects, compared to perspect doesn't have a + // convergence point, meaning that the "eye" of the camera is more like a plane than a point. + Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); + + // Calculate normalized direction vector + Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); + + if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position; + else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; + + // Apply calculated vectors to ray + ray.direction = direction; + + return ray; +} + +// Get transform matrix for camera +Matrix GetCameraMatrix(Camera camera) +{ + return MatrixLookAt(camera.position, camera.target, camera.up); +} + +// Returns camera 2d transform matrix +Matrix GetCameraMatrix2D(Camera2D camera) +{ + Matrix matTransform = { 0 }; + // The camera in world-space is set by + // 1. Move it to target + // 2. Rotate by -rotation and scale by (1/zoom) + // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), + // not for the camera getting bigger, hence the invert. Same deal with rotation. + // 3. Move it by (-offset); + // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) + // we need to do it into opposite direction (inverse transform) + + // Having camera transform in world-space, inverse of it gives the modelview transform. + // Since (A*B*C)' = C'*B'*A', the modelview is + // 1. Move to offset + // 2. Rotate and Scale + // 3. Move by -target + Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); + Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); + Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); + Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); + + matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); + + return matTransform; +} + +// Returns the screen space position from a 3d world space position +Vector2 GetWorldToScreen(Vector3 position, Camera camera) +{ + Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight()); + + return screenPosition; +} + +// Returns size position for a 3d world space position (useful for texture drawing) +Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) +{ + // Calculate projection matrix (from perspective instead of frustum + Matrix matProj = MatrixIdentity(); + + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + double top = camera.fovy/2.0; + double right = top*aspect; + + // Calculate projection matrix from orthographic + matProj = MatrixOrtho(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + + // Calculate view matrix from camera look at (and transpose it) + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + // Convert world position vector to quaternion + Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; + + // Transform world position to view + worldPos = QuaternionTransform(worldPos, matView); + + // Transform result to projection (clip space position) + worldPos = QuaternionTransform(worldPos, matProj); + + // Calculate normalized device coordinates (inverted y) + Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w }; + + // Calculate 2d screen position vector + Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height }; + + return screenPosition; +} + +// Returns the screen space position for a 2d camera world space position +Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) +{ + Matrix matCamera = GetCameraMatrix2D(camera); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); + + return (Vector2){ transform.x, transform.y }; +} + +// Returns the world space position for a 2d camera screen space position +Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) +{ + Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); + + return (Vector2){ transform.x, transform.y }; +} + +// Set target FPS (maximum) +void SetTargetFPS(int fps) +{ + if (fps < 1) CORE.Time.target = 0.0; + else CORE.Time.target = 1.0/(double)fps; + + TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000); +} + +// Returns current FPS +// NOTE: We calculate an average framerate +int GetFPS(void) +{ + #define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures + #define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 millisecondes + #define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT) + + static int index = 0; + static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 }; + static float average = 0, last = 0; + float fpsFrame = GetFrameTime(); + + if (fpsFrame == 0) return 0; + + if ((GetTime() - last) > FPS_STEP) + { + last = (float)GetTime(); + index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT; + average -= history[index]; + history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT; + average += history[index]; + } + + return (int)roundf(1.0f/average); +} + +// Returns time in seconds for last frame drawn (delta time) +float GetFrameTime(void) +{ + return (float)CORE.Time.frame; +} + +// Get elapsed time measure in seconds since InitTimer() +// NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow() +// NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit() +double GetTime(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + return glfwGetTime(); // Elapsed time since glfwInit() +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + + return (double)(time - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() +#endif + +#if defined(PLATFORM_UWP) + return UWPGetQueryTimeFunc()(); +#endif +} + +// Setup window configuration flags (view FLAGS) +// NOTE: This function is expected to be called before window creation, +// because it setups some flags for the window creation process. +// To configure window states after creation, just use SetWindowState() +void SetConfigFlags(unsigned int flags) +{ + // Selected flags are set but not evaluated at this point, + // flag evaluation happens at InitWindow() or SetWindowState() + CORE.Window.flags |= flags; +} + +// NOTE TRACELOG() function is located in [utils.h] + +// Takes a screenshot of current screen (saved a .png) +// NOTE: This function could work in any platform but some platforms: PLATFORM_ANDROID and PLATFORM_WEB +// have their own internal file-systems, to dowload image to user file-system some additional mechanism is required +void TakeScreenshot(const char *fileName) +{ + unsigned char *imgData = rlReadScreenPixels(CORE.Window.render.width, CORE.Window.render.height); + Image image = { imgData, CORE.Window.render.width, CORE.Window.render.height, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + + char path[512] = { 0 }; +#if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, "/"); + strcat(path, fileName); +#elif defined(PLATFORM_UWP) + strcpy(path, CORE.UWP.internalDataPath); + strcat(path, "/"); + strcat(path, fileName); +#else + strcpy(path, fileName); +#endif + + ExportImage(image, path); + RL_FREE(imgData); + +#if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", GetFileName(path), GetFileName(path))); +#endif + + // TODO: Verification required for log + TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); +} + +// Returns a random value between min and max (both included) +int GetRandomValue(int min, int max) +{ + if (min > max) + { + int tmp = max; + max = min; + min = tmp; + } + + return (rand()%(abs(max - min) + 1) + min); +} + +// Check if the file exists +bool FileExists(const char *fileName) +{ + bool result = false; + +#if defined(_WIN32) + if (_access(fileName, 0) != -1) result = true; +#else + if (access(fileName, F_OK) != -1) result = true; +#endif + + return result; +} + +// Check file extension +// NOTE: Extensions checking is not case-sensitive +bool IsFileExtension(const char *fileName, const char *ext) +{ + bool result = false; + const char *fileExt = GetFileExtension(fileName); + + if (fileExt != NULL) + { +#if defined(SUPPORT_TEXT_MANIPULATION) + int extCount = 0; + const char **checkExts = TextSplit(ext, ';', &extCount); + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileExt)); + + for (int i = 0; i < extCount; i++) + { + if (TextIsEqual(fileExtLower, TextToLower(checkExts[i]))) + { + result = true; + break; + } + } +#else + if (strcmp(fileExt, ext) == 0) result = true; +#endif + } + + return result; +} + +// Check if a directory path exists +bool DirectoryExists(const char *dirPath) +{ + bool result = false; + DIR *dir = opendir(dirPath); + + if (dir != NULL) + { + result = true; + closedir(dir); + } + + return result; +} + +// Get pointer to extension for a filename string (includes the dot: .png) +const char *GetFileExtension(const char *fileName) +{ + const char *dot = strrchr(fileName, '.'); + + if (!dot || dot == fileName) return NULL; + + return dot; +} + +// String pointer reverse break: returns right-most occurrence of charset in s +static const char *strprbrk(const char *s, const char *charset) +{ + const char *latestMatch = NULL; + for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } + return latestMatch; +} + +// Get pointer to filename for a path string +const char *GetFileName(const char *filePath) +{ + const char *fileName = NULL; + if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); + + if (!fileName) return filePath; + + return fileName + 1; +} + +// Get filename string without extension (uses static string) +const char *GetFileNameWithoutExt(const char *filePath) +{ + #define MAX_FILENAMEWITHOUTEXT_LENGTH 128 + + static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH]; + memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); + + if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension + + int len = (int)strlen(fileName); + + for (int i = 0; (i < len) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) + { + if (fileName[i] == '.') + { + // NOTE: We break on first '.' found + fileName[i] = '\0'; + break; + } + } + + return fileName; +} + +// Get directory for a given filePath +const char *GetDirectoryPath(const char *filePath) +{ +/* + // NOTE: Directory separator is different in Windows and other platforms, + // fortunately, Windows also support the '/' separator, that's the one should be used + #if defined(_WIN32) + char separator = '\\'; + #else + char separator = '/'; + #endif +*/ + const char *lastSlash = NULL; + static char dirPath[MAX_FILEPATH_LENGTH]; + memset(dirPath, 0, MAX_FILEPATH_LENGTH); + + // In case provided path does not contain a root drive letter (C:\, D:\) nor leading path separator (\, /), + // we add the current directory path to dirPath + if (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/') + { + // For security, we set starting path to current directory, + // obtained path will be concated to this + dirPath[0] = '.'; + dirPath[1] = '/'; + } + + lastSlash = strprbrk(filePath, "\\/"); + if (lastSlash) + { + if (lastSlash == filePath) + { + // The last and only slash is the leading one: path is in a root directory + dirPath[0] = filePath[0]; + dirPath[1] = '\0'; + } + else + { + // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' + memcpy(dirPath + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1)); + dirPath[strlen(filePath) - strlen(lastSlash) + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0)] = '\0'; // Add '\0' manually + } + } + + return dirPath; +} + +// Get previous directory path for a given path +const char *GetPrevDirectoryPath(const char *dirPath) +{ + static char prevDirPath[MAX_FILEPATH_LENGTH]; + memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); + int pathLen = (int)strlen(dirPath); + + if (pathLen <= 3) strcpy(prevDirPath, dirPath); + + for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--) + { + if ((dirPath[i] == '\\') || (dirPath[i] == '/')) + { + // Check for root: "C:\" or "/" + if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++; + + strncpy(prevDirPath, dirPath, i); + break; + } + } + + return prevDirPath; +} + +// Get current working directory +const char *GetWorkingDirectory(void) +{ + static char currentDir[MAX_FILEPATH_LENGTH]; + memset(currentDir, 0, MAX_FILEPATH_LENGTH); + + char *ptr = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); + + return ptr; +} + +// Get filenames in a directory path (max 512 files) +// NOTE: Files count is returned by parameters pointer +char **GetDirectoryFiles(const char *dirPath, int *fileCount) +{ + #define MAX_DIRECTORY_FILES 512 + + ClearDirectoryFiles(); + + // Memory allocation for MAX_DIRECTORY_FILES + dirFilesPath = (char **)RL_MALLOC(sizeof(char *)*MAX_DIRECTORY_FILES); + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); + + int counter = 0; + struct dirent *entity; + DIR *dir = opendir(dirPath); + + if (dir != NULL) // It's a directory + { + // TODO: Reading could be done in two passes, + // first one to count files and second one to read names + // That way we can allocate required memory, instead of a limited pool + + while ((entity = readdir(dir)) != NULL) + { + strcpy(dirFilesPath[counter], entity->d_name); + counter++; + } + + closedir(dir); + } + else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file... + + dirFilesCount = counter; + *fileCount = dirFilesCount; + + return dirFilesPath; +} + +// Clear directory files paths buffers +void ClearDirectoryFiles(void) +{ + if (dirFilesCount > 0) + { + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) RL_FREE(dirFilesPath[i]); + + RL_FREE(dirFilesPath); + } + + dirFilesCount = 0; +} + +// Change working directory, returns true on success +bool ChangeDirectory(const char *dir) +{ + bool result = CHDIR(dir); + + if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir); + + return (result == 0); +} + +// Check if a file has been dropped into window +bool IsFileDropped(void) +{ + if (CORE.Window.dropFilesCount > 0) return true; + else return false; +} + +// Get dropped files names +char **GetDroppedFiles(int *count) +{ + *count = CORE.Window.dropFilesCount; + return CORE.Window.dropFilesPath; +} + +// Clear dropped files paths buffer +void ClearDroppedFiles(void) +{ + if (CORE.Window.dropFilesCount > 0) + { + for (int i = 0; i < CORE.Window.dropFilesCount; i++) RL_FREE(CORE.Window.dropFilesPath[i]); + + RL_FREE(CORE.Window.dropFilesPath); + + CORE.Window.dropFilesCount = 0; + } +} + +// Get file modification time (last write time) +long GetFileModTime(const char *fileName) +{ + struct stat result = { 0 }; + + if (stat(fileName, &result) == 0) + { + time_t mod = result.st_mtime; + + return (long)mod; + } + + return 0; +} + +// Compress data (DEFLATE algorythm) +unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength) +{ + #define COMPRESSION_QUALITY_DEFLATE 8 + + unsigned char *compData = NULL; + +#if defined(SUPPORT_COMPRESSION_API) + // Compress data and generate a valid DEFLATE stream + struct sdefl sdefl = { 0 }; + int bounds = sdefl_bound(dataLength); + compData = (unsigned char *)RL_CALLOC(bounds, 1); + *compDataLength = sdeflate(&sdefl, compData, data, dataLength, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbwi + + TraceLog(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataLength, compDataLength); +#endif + + return compData; +} + +// Decompress data (DEFLATE algorythm) +unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength) +{ + unsigned char *data = NULL; + +#if defined(SUPPORT_COMPRESSION_API) + // Decompress data from a valid DEFLATE stream + data = RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); + int length = sinflate(data, compData, compDataLength); + unsigned char *temp = RL_REALLOC(data, length); + + if (temp != NULL) data = temp; + else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory"); + + *dataLength = length; + + TraceLog(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataLength, dataLength); +#endif + + return data; +} + +// Save integer value to storage file (to defined position) +// NOTE: Storage positions is directly related to file memory layout (4 bytes each integer) +bool SaveStorageValue(unsigned int position, int value) +{ + bool success = false; + +#if defined(SUPPORT_DATA_STORAGE) + char path[512] = { 0 }; +#if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#elif defined(PLATFORM_UWP) + strcpy(path, CORE.UWP.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#else + strcpy(path, STORAGE_DATA_FILE); +#endif + + unsigned int dataSize = 0; + unsigned int newDataSize = 0; + unsigned char *fileData = LoadFileData(path, &dataSize); + unsigned char *newFileData = NULL; + + if (fileData != NULL) + { + if (dataSize <= (position*sizeof(int))) + { + // Increase data size up to position and store value + newDataSize = (position + 1)*sizeof(int); + newFileData = (unsigned char *)RL_REALLOC(fileData, newDataSize); + + if (newFileData != NULL) + { + // RL_REALLOC succeded + int *dataPtr = (int *)newFileData; + dataPtr[position] = value; + } + else + { + // RL_REALLOC failed + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size", path, dataSize, position*sizeof(int)); + + // We store the old size of the file + newFileData = fileData; + newDataSize = dataSize; + } + } + else + { + // Store the old size of the file + newFileData = fileData; + newDataSize = dataSize; + + // Replace value on selected position + int *dataPtr = (int *)newFileData; + dataPtr[position] = value; + } + + success = SaveFileData(path, newFileData, newDataSize); + RL_FREE(newFileData); + } + else + { + TRACELOG(LOG_INFO, "FILEIO: [%s] File not found, creating it", path); + + dataSize = (position + 1)*sizeof(int); + fileData = (unsigned char *)RL_MALLOC(dataSize); + int *dataPtr = (int *)fileData; + dataPtr[position] = value; + + success = SaveFileData(path, fileData, dataSize); + UnloadFileData(fileData); + } +#endif + + return success; +} + +// Load integer value from storage file (from defined position) +// NOTE: If requested position could not be found, value 0 is returned +int LoadStorageValue(unsigned int position) +{ + int value = 0; + +#if defined(SUPPORT_DATA_STORAGE) + char path[512] = { 0 }; +#if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#elif defined(PLATFORM_UWP) + strcpy(path, CORE.UWP.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#else + strcpy(path, STORAGE_DATA_FILE); +#endif + + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(path, &dataSize); + + if (fileData != NULL) + { + if (dataSize < (position*4)) TRACELOG(LOG_WARNING, "SYSTEM: Failed to find storage position"); + else + { + int *dataPtr = (int *)fileData; + value = dataPtr[position]; + } + + UnloadFileData(fileData); + } +#endif + return value; +} + +// Open URL with default system browser (if available) +// NOTE: This function is only safe to use if you control the URL given. +// A user could craft a malicious string performing another action. +// Only call this function yourself not with user input or make sure to check the string yourself. +// Ref: https://github.com/raysan5/raylib/issues/686 +void OpenURL(const char *url) +{ + // Small security check trying to avoid (partially) malicious code... + // sorry for the inconvenience when you hit this point... + if (strchr(url, '\'') != NULL) + { + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL is not valid"); + } + else + { +#if defined(PLATFORM_DESKTOP) + char *cmd = (char *)RL_CALLOC(strlen(url) + 10, sizeof(char)); + #if defined(_WIN32) + sprintf(cmd, "explorer %s", url); + #endif + #if defined(__linux__) || defined(__FreeBSD__) + sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser + #endif + #if defined(__APPLE__) + sprintf(cmd, "open '%s'", url); + #endif + system(cmd); + RL_FREE(cmd); +#endif +#if defined(PLATFORM_WEB) + emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); +#endif + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions +//---------------------------------------------------------------------------------- +// Detect if a key has been pressed once +bool IsKeyPressed(int key) +{ + bool pressed = false; + + if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true; + else pressed = false; + + return pressed; +} + +// Detect if a key is being pressed (key held down) +bool IsKeyDown(int key) +{ + if (CORE.Input.Keyboard.currentKeyState[key] == 1) return true; + else return false; +} + +// Detect if a key has been released once +bool IsKeyReleased(int key) +{ + bool released = false; + + if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true; + else released = false; + + return released; +} + +// Detect if a key is NOT being pressed (key not held down) +bool IsKeyUp(int key) +{ + if (CORE.Input.Keyboard.currentKeyState[key] == 0) return true; + else return false; +} + +// Get the last key pressed +int GetKeyPressed(void) +{ + int value = 0; + + if (CORE.Input.Keyboard.keyPressedQueueCount > 0) + { + // Get character from the queue head + value = CORE.Input.Keyboard.keyPressedQueue[0]; + + // Shift elements 1 step toward the head. + for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) + CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; + + // Reset last character in the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 0; + CORE.Input.Keyboard.keyPressedQueueCount--; + } + + return value; +} + +// Get the last char pressed +int GetCharPressed(void) +{ + int value = 0; + + if (CORE.Input.Keyboard.charPressedQueueCount > 0) + { + // Get character from the queue head + value = CORE.Input.Keyboard.charPressedQueue[0]; + + // Shift elements 1 step toward the head. + for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++) + CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1]; + + // Reset last character in the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = 0; + CORE.Input.Keyboard.charPressedQueueCount--; + } + + return value; +} + +// Set a custom key to exit program +// NOTE: default exitKey is ESCAPE +void SetExitKey(int key) +{ +#if !defined(PLATFORM_ANDROID) + CORE.Input.Keyboard.exitKey = key; +#endif +} + +// NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB) + +// Detect if a gamepad is available +bool IsGamepadAvailable(int gamepad) +{ + bool result = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; + + return result; +} + +// Check gamepad name (if available) +bool IsGamepadName(int gamepad, const char *name) +{ + bool result = false; + const char *currentName = NULL; + + if (CORE.Input.Gamepad.ready[gamepad]) currentName = GetGamepadName(gamepad); + if ((name != NULL) && (currentName != NULL)) result = (strcmp(name, currentName) == 0); + + return result; +} + +// Return gamepad internal name id +const char *GetGamepadName(int gamepad) +{ +#if defined(PLATFORM_DESKTOP) + if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad); + else return NULL; +#endif +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name); + return CORE.Input.Gamepad.name; +#endif + return NULL; +} + +// Return gamepad axis count +int GetGamepadAxisCount(int gamepad) +{ +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + int axisCount = 0; + if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount); + CORE.Input.Gamepad.axisCount = axisCount; +#endif + + return CORE.Input.Gamepad.axisCount; +} + +// Return axis movement vector for a gamepad +float GetGamepadAxisMovement(int gamepad, int axis) +{ + float value = 0; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) && + (fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) > 0.1f)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA + + return value; +} + +// Detect if a gamepad button has been pressed once +bool IsGamepadButtonPressed(int gamepad, int button) +{ + bool pressed = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.previousState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) pressed = true; + else pressed = false; + + return pressed; +} + +// Detect if a gamepad button is being pressed +bool IsGamepadButtonDown(int gamepad, int button) +{ + bool result = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) result = true; + + return result; +} + +// Detect if a gamepad button has NOT been pressed once +bool IsGamepadButtonReleased(int gamepad, int button) +{ + bool released = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.previousState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) released = true; + else released = false; + + return released; +} + +// Detect if a gamepad button is NOT being pressed +bool IsGamepadButtonUp(int gamepad, int button) +{ + bool result = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) result = true; + + return result; +} + +// Get the last gamepad button pressed +int GetGamepadButtonPressed(void) +{ + return CORE.Input.Gamepad.lastButtonPressed; +} + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + int result = 0; + +#if defined(PLATFORM_DESKTOP) + result = glfwUpdateGamepadMappings(mappings); +#endif + + return result; +} + +// Detect if a mouse button has been pressed once +bool IsMouseButtonPressed(int button) +{ + bool pressed = false; + + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; + + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; + + return pressed; +} + +// Detect if a mouse button is being pressed +bool IsMouseButtonDown(int button) +{ + bool down = false; + + if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; + + // Map touches to mouse buttons checking + if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; + + return down; +} + +// Detect if a mouse button has been released once +bool IsMouseButtonReleased(int button) +{ + bool released = false; + + if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; + + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; + + return released; +} + +// Detect if a mouse button is NOT being pressed +bool IsMouseButtonUp(int button) +{ + return !IsMouseButtonDown(button); +} + +// Returns mouse position X +int GetMouseX(void) +{ +#if defined(PLATFORM_ANDROID) + return (int)CORE.Input.Touch.position[0].x; +#else + return (int)((CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); +#endif +} + +// Returns mouse position Y +int GetMouseY(void) +{ +#if defined(PLATFORM_ANDROID) + return (int)CORE.Input.Touch.position[0].y; +#else + return (int)((CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); +#endif +} + +// Returns mouse position XY +Vector2 GetMousePosition(void) +{ + Vector2 position = { 0 }; + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) + position = GetTouchPosition(0); +#else + position.x = (CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x; + position.y = (CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y; +#endif + + return position; +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.position = (Vector2){ (float)x, (float)y }; +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // NOTE: emscripten not implemented + glfwSetCursorPos(CORE.Window.handle, CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseSetPosFunc()(x, y); +#endif +} + +// Set mouse offset +// NOTE: Useful when rendering to different size targets +void SetMouseOffset(int offsetX, int offsetY) +{ + CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY }; +} + +// Set mouse scaling +// NOTE: Useful when rendering to different size targets +void SetMouseScale(float scaleX, float scaleY) +{ + CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY }; +} + +// Returns mouse wheel movement Y +float GetMouseWheelMove(void) +{ +#if defined(PLATFORM_ANDROID) + return 0.0f; +#endif +#if defined(PLATFORM_WEB) + return CORE.Input.Mouse.previousWheelMove/100.0f; +#endif + + return CORE.Input.Mouse.previousWheelMove; +} + +// Set mouse cursor +// NOTE: This is a no-op on platforms other than PLATFORM_DESKTOP +void SetMouseCursor(int cursor) +{ +#if defined(PLATFORM_DESKTOP) + CORE.Input.Mouse.cursor = cursor; + if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(CORE.Window.handle, NULL); + else + { + // NOTE: We are relating internal GLFW enum values to our MouseCursor enum values + glfwSetCursor(CORE.Window.handle, glfwCreateStandardCursor(0x00036000 + cursor)); + } +#endif +} + +// Returns touch position X for touch point 0 (relative to screen size) +int GetTouchX(void) +{ +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return (int)CORE.Input.Touch.position[0].x; +#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM + return GetMouseX(); +#endif +} + +// Returns touch position Y for touch point 0 (relative to screen size) +int GetTouchY(void) +{ +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return (int)CORE.Input.Touch.position[0].y; +#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM + return GetMouseY(); +#endif +} + +// Returns touch position XY for a touch point index (relative to screen size) +// TODO: Touch position should be scaled depending on display size and render size +Vector2 GetTouchPosition(int index) +{ + Vector2 position = { -1.0f, -1.0f }; + +#if defined(PLATFORM_DESKTOP) + // TODO: GLFW does not support multi-touch input just yet + // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch + // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages + if (index == 0) position = GetMousePosition(); +#endif +#if defined(PLATFORM_ANDROID) + if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; + else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); + + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + position.x = position.x*((float)CORE.Window.screen.width/(float)(CORE.Window.display.width - CORE.Window.renderOffset.x)) - CORE.Window.renderOffset.x/2; + position.y = position.y*((float)CORE.Window.screen.height/(float)(CORE.Window.display.height - CORE.Window.renderOffset.y)) - CORE.Window.renderOffset.y/2; + } + else + { + position.x = position.x*((float)CORE.Window.render.width/(float)CORE.Window.display.width) - CORE.Window.renderOffset.x/2; + position.y = position.y*((float)CORE.Window.render.height/(float)CORE.Window.display.height) - CORE.Window.renderOffset.y/2; + } +#endif +#if defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; + else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); + + // TODO: Touch position scaling required? +#endif + + return position; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize display device and framebuffer +// NOTE: width and height represent the screen (framebuffer) desired size, not actual display size +// If width or height are 0, default display size will be used for framebuffer size +// NOTE: returns false in case graphic device could not be created +static bool InitGraphicsDevice(int width, int height) +{ + CORE.Window.screen.width = width; // User desired width + CORE.Window.screen.height = height; // User desired height + CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default + + // NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars... + // ...in top-down or left-right to match display aspect ratio (no weird scalings) + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + glfwSetErrorCallback(ErrorCallback); + +#if defined(__APPLE__) + glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); +#endif + + if (!glfwInit()) + { + TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW"); + return false; + } + + // NOTE: Getting video modes is not implemented in emscripten GLFW3 version +#if defined(PLATFORM_DESKTOP) + // Find monitor resolution + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + if (!monitor) + { + TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); + return false; + } + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + + // Screen size security check + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; +#endif // PLATFORM_DESKTOP + +#if defined(PLATFORM_WEB) + CORE.Window.display.width = CORE.Window.screen.width; + CORE.Window.display.height = CORE.Window.screen.height; +#endif // PLATFORM_WEB + + glfwDefaultWindowHints(); // Set default windows hints + //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits + //glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits + //glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits + //glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits + //glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits + //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window + //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API + //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers + + // Check window creation flags + if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; + + if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window + else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden + + if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window + else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window + + if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window + else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable + + // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization + if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + + // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization + if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + + if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); + else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); + + if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); + + // NOTE: Some GLFW flags are not supported on HTML5 +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer + else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer + + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // Resize window content area based on the monitor content scale. + // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11. + // On platforms like macOS the resolution of the framebuffer is changed independently of the window size. + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on + #if defined(__APPLE__) + glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); + #endif + } + else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); +#endif + + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 + } + + // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version + // with forward compatibility to older OpenGL versions. + // For example, if using OpenGL 1.1, driver can provide a 4.3 context forward compatible. + + // Check selection OpenGL version + if (rlGetVersion() == OPENGL_21) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) + } + else if (rlGetVersion() == OPENGL_33) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! + // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE +#if defined(__APPLE__) + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires fordward compatibility +#else + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! +#endif + //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context + } + else if (rlGetVersion() == OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); +#if defined(PLATFORM_DESKTOP) + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); +#else + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); +#endif + } + +#if defined(PLATFORM_DESKTOP) + // NOTE: GLFW 3.4+ defers initialization of the Joystick subsystem on the first call to any Joystick related functions. + // Forcing this initialization here avoids doing it on `PollInputEvents` called by `EndDrawing` after first frame has been just drawn. + // The initialization will still happen and possible delays still occur, but before the window is shown, which is a nicer experience. + // REF: https://github.com/raysan5/raylib/issues/1554 + if (MAX_GAMEPADS > 0) glfwSetJoystickCallback(NULL); +#endif + + if (CORE.Window.fullscreen) + { + // remember center for switchinging from fullscreen to window + CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2; + CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2; + + if (CORE.Window.position.x < 0) CORE.Window.position.x = 0; + if (CORE.Window.position.y < 0) CORE.Window.position.y = 0; + + // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); + + // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height + for (int i = 0; i < count; i++) + { + if ((unsigned int)modes[i].width >= CORE.Window.screen.width) + { + if ((unsigned int)modes[i].height >= CORE.Window.screen.height) + { + CORE.Window.display.width = modes[i].width; + CORE.Window.display.height = modes[i].height; + break; + } + } + } + +#if defined(PLATFORM_DESKTOP) + // If we are windowed fullscreen, ensures that window does not minimize when focus is lost + if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) + { + glfwWindowHint(GLFW_AUTO_ICONIFY, 0); + } +#endif + TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + + // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, + // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), + // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched + // by the sides to fit all monitor space... + + // Try to setup the most appropiate fullscreen framebuffer for the requested screenWidth/screenHeight + // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) + // Modified global variables: CORE.Window.screen.width/CORE.Window.screen.height - CORE.Window.render.width/CORE.Window.render.height - CORE.Window.renderOffset.x/CORE.Window.renderOffset.y - CORE.Window.screenScale + // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... + // HighDPI monitors are properly considered in a following similar function: SetupViewport() + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + CORE.Window.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL); + + // NOTE: Full-screen change, not working properly... + //glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + else + { + // No-fullscreen window creation + CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); + + if (CORE.Window.handle) + { +#if defined(PLATFORM_DESKTOP) + // Center window on screen + int windowPosX = CORE.Window.display.width/2 - CORE.Window.screen.width/2; + int windowPosY = CORE.Window.display.height/2 - CORE.Window.screen.height/2; + + if (windowPosX < 0) windowPosX = 0; + if (windowPosY < 0) windowPosY = 0; + + glfwSetWindowPos(CORE.Window.handle, windowPosX, windowPosY); +#endif + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + } + } + + if (!CORE.Window.handle) + { + glfwTerminate(); + TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); + return false; + } + else + { + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); +#if defined(PLATFORM_DESKTOP) + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); +#endif + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } + + // Set window callback events + glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! +#if !defined(PLATFORM_WEB) + glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback); +#endif + glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback); + glfwSetWindowFocusCallback(CORE.Window.handle, WindowFocusCallback); + glfwSetDropCallback(CORE.Window.handle, WindowDropCallback); + // Set input callback events + glfwSetKeyCallback(CORE.Window.handle, KeyCallback); + glfwSetCharCallback(CORE.Window.handle, CharCallback); + glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback); + glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes + glfwSetScrollCallback(CORE.Window.handle, MouseScrollCallback); + glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback); + + glfwMakeContextCurrent(CORE.Window.handle); + +#if !defined(PLATFORM_WEB) + glfwSwapInterval(0); // No V-Sync by default +#endif + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration + if (CORE.Window.flags & FLAG_VSYNC_HINT) + { + // WARNING: It seems to hits a critical render path in Intel HD Graphics + glfwSwapInterval(1); + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC"); + } +#endif // PLATFORM_DESKTOP || PLATFORM_WEB + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + CORE.Window.fullscreen = true; + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + +#if defined(PLATFORM_RPI) + bcm_host_init(); + + DISPMANX_ELEMENT_HANDLE_T dispmanElement; + DISPMANX_DISPLAY_HANDLE_T dispmanDisplay; + DISPMANX_UPDATE_HANDLE_T dispmanUpdate; + + VC_RECT_T dstRect; + VC_RECT_T srcRect; +#endif + +#if defined(PLATFORM_DRM) + CORE.Window.fd = -1; + CORE.Window.connector = NULL; + CORE.Window.modeIndex = -1; + CORE.Window.crtc = NULL; + CORE.Window.gbmDevice = NULL; + CORE.Window.gbmSurface = NULL; + CORE.Window.prevBO = NULL; + CORE.Window.prevFB = 0; + +#if defined(DEFAULT_GRAPHIC_DEVICE_DRM) + CORE.Window.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR); +#else + TRACELOG(LOG_INFO, "DISPLAY: No graphic card set, trying card1"); + CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // VideoCore VI (Raspberry Pi 4) + if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) + { + TRACELOG(LOG_INFO, "DISPLAY: Failed to open graphic card1, trying card0"); + CORE.Window.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3) + } +#endif + if (-1 == CORE.Window.fd) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card"); + return false; + } + + drmModeRes *res = drmModeGetResources(CORE.Window.fd); + if (!res) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources"); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors); + for (size_t i = 0; i < res->count_connectors; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i); + drmModeConnector *con = drmModeGetConnector(CORE.Window.fd, res->connectors[i]); + TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes); + if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected"); + CORE.Window.connector = con; + break; + } + else + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode NOT connected (deleting)"); + drmModeFreeConnector(con); + } + } + if (!CORE.Window.connector) + { + TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found"); + drmModeFreeResources(res); + return false; + } + + drmModeEncoder *enc = drmModeGetEncoder(CORE.Window.fd, CORE.Window.connector->encoder_id); + if (!enc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder"); + drmModeFreeResources(res); + return false; + } + + CORE.Window.crtc = drmModeGetCrtc(CORE.Window.fd, enc->crtc_id); + if (!CORE.Window.crtc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + // If InitWindow should use the current mode find it in the connector's mode list + if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0)) + { + TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode..."); + + CORE.Window.modeIndex = FindMatchingConnectorMode(CORE.Window.connector, &CORE.Window.crtc->mode); + + if (CORE.Window.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT; + const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60; + // try to find an exact matching mode + CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + // if nothing found, try to find a nearly matching mode + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + // if nothing found, try to find an exactly matching mode including interlaced + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + // if nothing found, try to find a nearly matching mode including interlaced + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + // if nothing found, there is no suitable mode + if (CORE.Window.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + CORE.Window.display.width = CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay; + CORE.Window.display.height = CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay; + + TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", CORE.Window.connector->modes[CORE.Window.modeIndex].name, + CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, + (CORE.Window.connector->modes[CORE.Window.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', + CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh); + + // Use the width and height of the surface for render + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + + drmModeFreeEncoder(enc); + enc = NULL; + + drmModeFreeResources(res); + res = NULL; + + CORE.Window.gbmDevice = gbm_create_device(CORE.Window.fd); + if (!CORE.Window.gbmDevice) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device"); + return false; + } + + CORE.Window.gbmSurface = gbm_surface_create(CORE.Window.gbmDevice, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, + CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!CORE.Window.gbmSurface) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface"); + return false; + } +#endif + + EGLint samples = 0; + EGLint sampleBuffer = 0; + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + samples = 4; + sampleBuffer = 1; + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + } + + const EGLint framebufferAttribs[] = + { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? +#if defined(PLATFORM_DRM) + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! +#endif + EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) + EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) + EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) +#if defined(PLATFORM_DRM) + EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) +#endif + //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) + EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) + //EGL_STENCIL_SIZE, 8, // Stencil buffer size + EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA + EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) + EGL_NONE + }; + + const EGLint contextAttribs[] = + { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + +#if defined(PLATFORM_UWP) + const EGLint surfaceAttributes[] = + { + // EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER is part of the same optimization as EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER (see above). + // If you have compilation issues with it then please update your Visual Studio templates. + EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER, EGL_TRUE, + EGL_NONE + }; + + const EGLint defaultDisplayAttributes[] = + { + // These are the default display attributes, used to request ANGLE's D3D11 renderer. + // eglInitialize will only succeed with these attributes if the hardware supports D3D11 Feature Level 10_0+. + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + + // EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER is an optimization that can have large performance benefits on mobile devices. + // Its syntax is subject to change, though. Please update your Visual Studio templates if you experience compilation issues with it. + EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, + + // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that enables ANGLE to automatically call + // the IDXGIDevice3::Trim method on behalf of the application when it gets suspended. + // Calling IDXGIDevice3::Trim when an application is suspended is a Windows Store application certification requirement. + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, + EGL_NONE, + }; + + const EGLint fl9_3DisplayAttributes[] = + { + // These can be used to request ANGLE's D3D11 renderer, with D3D11 Feature Level 9_3. + // These attributes are used if the call to eglInitialize fails with the default display attributes. + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, 9, + EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, 3, + EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, + EGL_NONE, + }; + + const EGLint warpDisplayAttributes[] = + { + // These attributes can be used to request D3D11 WARP. + // They are used if eglInitialize fails with both the default display attributes and the 9_3 display attributes. + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE, + EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, + EGL_NONE, + }; + + // eglGetPlatformDisplayEXT is an alternative to eglGetDisplay. It allows us to pass in display attributes, used to configure D3D11. + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)(eglGetProcAddress("eglGetPlatformDisplayEXT")); + if (!eglGetPlatformDisplayEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get function pointer: eglGetPlatformDisplayEXT()"); + return false; + } + + // + // To initialize the display, we make three sets of calls to eglGetPlatformDisplayEXT and eglInitialize, with varying + // parameters passed to eglGetPlatformDisplayEXT: + // 1) The first calls uses "defaultDisplayAttributes" as a parameter. This corresponds to D3D11 Feature Level 10_0+. + // 2) If eglInitialize fails for step 1 (e.g. because 10_0+ isn't supported by the default GPU), then we try again + // using "fl9_3DisplayAttributes". This corresponds to D3D11 Feature Level 9_3. + // 3) If eglInitialize fails for step 2 (e.g. because 9_3+ isn't supported by the default GPU), then we try again + // using "warpDisplayAttributes". This corresponds to D3D11 Feature Level 11_0 on WARP, a D3D11 software rasterizer. + // + + // This tries to initialize EGL to D3D11 Feature Level 10_0+. See above comment for details. + CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes); + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // This tries to initialize EGL to D3D11 Feature Level 9_3, if 10_0+ is unavailable (e.g. on some mobile devices). + CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, fl9_3DisplayAttributes); + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // This initializes EGL to D3D11 Feature Level 11_0 on WARP, if 9_3+ is unavailable on the default GPU. + CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, warpDisplayAttributes); + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + } + } + + EGLint numConfigs = 0; + if ((eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose first EGL configuration"); + return false; + } + + // Create a PropertySet and initialize with the EGLNativeWindowType. + //PropertySet^ surfaceCreationProperties = ref new PropertySet(); + //surfaceCreationProperties->Insert(ref new String(EGLNativeWindowTypeProperty), window); // CoreWindow^ window + + // You can configure the surface to render at a lower resolution and be scaled up to + // the full window size. The scaling is often free on mobile hardware. + // + // One way to configure the SwapChainPanel is to specify precisely which resolution it should render at. + // Size customRenderSurfaceSize = Size(800, 600); + // surfaceCreationProperties->Insert(ref new String(EGLRenderSurfaceSizeProperty), PropertyValue::CreateSize(customRenderSurfaceSize)); + // + // Another way is to tell the SwapChainPanel to render at a certain scale factor compared to its size. + // e.g. if the SwapChainPanel is 1920x1280 then setting a factor of 0.5f will make the app render at 960x640 + // float customResolutionScale = 0.5f; + // surfaceCreationProperties->Insert(ref new String(EGLRenderResolutionScaleProperty), PropertyValue::CreateSingle(customResolutionScale)); + + + // eglCreateWindowSurface() requires a EGLNativeWindowType parameter, + // In Windows platform: typedef HWND EGLNativeWindowType; + + + // Property: EGLNativeWindowTypeProperty + // Type: IInspectable + // Description: Set this property to specify the window type to use for creating a surface. + // If this property is missing, surface creation will fail. + // + //const wchar_t EGLNativeWindowTypeProperty[] = L"EGLNativeWindowTypeProperty"; + + //https://stackoverflow.com/questions/46550182/how-to-create-eglsurface-using-c-winrt-and-angle + + //CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, reinterpret_cast(surfaceCreationProperties), surfaceAttributes); + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType) UWPGetCoreWindowPtr(), surfaceAttributes); + if (CORE.Window.surface == EGL_NO_SURFACE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL fullscreen surface"); + return false; + } + + CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); + if (CORE.Window.context == EGL_NO_CONTEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); + return false; + } + + // Get EGL device window size + eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_WIDTH, &CORE.Window.screen.width); + eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_HEIGHT, &CORE.Window.screen.height); + + // Get display size + UWPGetDisplaySizeFunc()(&CORE.Window.display.width, &CORE.Window.display.height); + + // Use the width and height of the surface for render + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + +#endif // PLATFORM_UWP + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + EGLint numConfigs = 0; + + // Get an EGL device connection +#if defined(PLATFORM_DRM) + CORE.Window.device = eglGetDisplay((EGLNativeDisplayType)CORE.Window.gbmDevice); +#else + CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); +#endif + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + // Initialize the EGL device connection + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + +#if defined(PLATFORM_DRM) + if (!eglChooseConfig(CORE.Window.device, NULL, NULL, 0, &numConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError()); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs); + + EGLConfig *configs = RL_CALLOC(numConfigs, sizeof(*configs)); + if (!configs) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs"); + return false; + } + + EGLint matchingNumConfigs = 0; + if (!eglChooseConfig(CORE.Window.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError()); + free(configs); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs); + + // find the EGL config that matches the previously setup GBM format + int found = 0; + for (EGLint i = 0; i < matchingNumConfigs; ++i) + { + EGLint id = 0; + if (!eglGetConfigAttrib(CORE.Window.device, configs[i], EGL_NATIVE_VISUAL_ID, &id)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError()); + continue; + } + + if (GBM_FORMAT_ARGB8888 == id) + { + TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i); + CORE.Window.config = configs[i]; + found = 1; + break; + } + } + + RL_FREE(configs); + + if (!found) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config"); + return false; + } +#else + // Get an appropriate EGL framebuffer configuration + eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs); +#endif + + // Set rendering API + eglBindAPI(EGL_OPENGL_ES_API); + + // Create an EGL rendering context + CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); + if (CORE.Window.context == EGL_NO_CONTEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); + return false; + } +#endif + + // Create an EGL window surface + //--------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) + EGLint displayFormat = 0; + + // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() + // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID + eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); + + // At this point we need to manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + ANativeWindow_setBuffersGeometry(CORE.Android.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); + //ANativeWindow_setBuffersGeometry(CORE.Android.app->window, 0, 0, displayFormat); // Force use of native display size + + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, CORE.Android.app->window, NULL); +#endif // PLATFORM_ANDROID + +#if defined(PLATFORM_RPI) + graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height); + + // Screen size security check + if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; + + // At this point we need to manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + dstRect.x = 0; + dstRect.y = 0; + dstRect.width = CORE.Window.display.width; + dstRect.height = CORE.Window.display.height; + + srcRect.x = 0; + srcRect.y = 0; + srcRect.width = CORE.Window.render.width << 16; + srcRect.height = CORE.Window.render.height << 16; + + // NOTE: RPI dispmanx windowing system takes care of source rectangle scaling to destination rectangle by hardware (no cost) + // Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio + + VC_DISPMANX_ALPHA_T alpha; + alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; + //alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE; // TODO: Allow transparent framebuffer! -> FLAG_WINDOW_TRANSPARENT + alpha.opacity = 255; // Set transparency level for framebuffer, requires EGLAttrib: EGL_TRANSPARENT_TYPE + alpha.mask = 0; + + dispmanDisplay = vc_dispmanx_display_open(0); // LCD + dispmanUpdate = vc_dispmanx_update_start(0); + + dispmanElement = vc_dispmanx_element_add(dispmanUpdate, dispmanDisplay, 0/*layer*/, &dstRect, 0/*src*/, + &srcRect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE); + + CORE.Window.handle.element = dispmanElement; + CORE.Window.handle.width = CORE.Window.render.width; + CORE.Window.handle.height = CORE.Window.render.height; + vc_dispmanx_update_submit_sync(dispmanUpdate); + + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL); + + const unsigned char *const renderer = glGetString(GL_RENDERER); + if (renderer) TRACELOG(LOG_INFO, "DISPLAY: Renderer name is: %s", renderer); + else TRACELOG(LOG_WARNING, "DISPLAY: Failed to get renderer name"); + //--------------------------------------------------------------------------------- +#endif // PLATFORM_RPI + +#if defined(PLATFORM_DRM) + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL); + if (EGL_NO_SURFACE == CORE.Window.surface) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError()); + return false; + } + + // At this point we need to manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); +#endif // PLATFORM_DRM + + // There must be at least one frame displayed before the buffers are swapped + //eglSwapInterval(CORE.Window.device, 1); + + if (eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context) == EGL_FALSE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface"); + return false; + } + else + { + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } +#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + rlLoadExtensions(glfwGetProcAddress); +#else + rlLoadExtensions(eglGetProcAddress); +#endif + + // Initialize OpenGL context (states and resources) + // NOTE: CORE.Window.screen.width and CORE.Window.screen.height not used, just stored as globals in rlgl + rlglInit(CORE.Window.screen.width, CORE.Window.screen.height); + + int fbWidth = CORE.Window.render.width; + int fbHeight = CORE.Window.render.height; + +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling + // Framebuffer scaling should be activated with: glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); + #if !defined(__APPLE__) + glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight); + + // Screen scaling matrix is required in case desired screen area is different than display area + CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); + + // Mouse input scaling for the new screen size + SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); + #endif + } +#endif + + // Setup default viewport + SetupViewport(fbWidth, fbHeight); + + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; + + ClearBackground(RAYWHITE); // Default background color for raylib games :P + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_UWP) + CORE.Window.ready = true; +#endif + + if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); + + return true; +} + +// Set viewport for a provided width and height +static void SetupViewport(int width, int height) +{ + CORE.Window.render.width = width; + CORE.Window.render.height = height; + + // Set viewport width and height + // NOTE: We consider render size (scaled) and offset in case black bars are required and + // render area does not match full display area (this situation is only applicable on fullscreen mode) +#if defined(__APPLE__) + float xScale = 1.0f, yScale = 1.0f; + glfwGetWindowContentScale(CORE.Window.handle, &xScale, &yScale); + rlViewport(CORE.Window.renderOffset.x/2*xScale, CORE.Window.renderOffset.y/2*yScale, (CORE.Window.render.width - CORE.Window.renderOffset.x)*xScale, (CORE.Window.render.height - CORE.Window.renderOffset.y)*yScale); +#else + rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width - CORE.Window.renderOffset.x, CORE.Window.render.height - CORE.Window.renderOffset.y); +#endif + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlLoadIdentity(); // Reset current matrix (projection) + + // Set orthographic projection to current framebuffer size + // NOTE: Configured top-left corner as (0, 0) + rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) +} + +// Compute framebuffer size relative to screen size and display size +// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified +static void SetupFramebuffer(int width, int height) +{ + // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Downscaling required: Screen size (%ix%i) is bigger than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + // Downscaling to fit display with border-bars + float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; + float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; + + if (widthRatio <= heightRatio) + { + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); + CORE.Window.render.height = CORE.Window.display.height; + CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); + CORE.Window.renderOffset.y = 0; + } + + // Screen scaling required + float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; + CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); + + // NOTE: We render to full display resolution! + // We just need to calculate above parameters for downscale matrix and offsets + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = CORE.Window.display.height; + + TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); + } + else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) + { + // Required screen size is smaller than display size + TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + // Upscaling to fit display with border-bars + float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; + float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + + if (displayRatio <= screenRatio) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); + CORE.Window.renderOffset.y = 0; + } + } + else + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = 0; + } +} + +// Initialize hi-resolution timer +static void InitTimer(void) +{ + srand((unsigned int)time(NULL)); // Initialize random seed + +// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. +// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. +// High resolutions can also prevent the CPU power management system from entering power-saving modes. +// Setting a higher resolution does not improve the accuracy of the high-resolution performance counter. +#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) + timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + struct timespec now = { 0 }; + + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + { + CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; + } + else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); +#endif + + CORE.Time.previous = GetTime(); // Get time as double +} + +// Wait for some milliseconds (stop program execution) +// NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could +// take longer than expected... for that reason we use the busy wait loop +// Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected +// Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timming on Win32! +static void Wait(float ms) +{ +#if defined(PLATFORM_UWP) + UWPGetSleepFunc()(ms/1000); + return; +#endif + +#if defined(SUPPORT_BUSY_WAIT_LOOP) + double prevTime = GetTime(); + double nextTime = 0.0; + + // Busy wait loop + while ((nextTime - prevTime) < ms/1000.0f) nextTime = GetTime(); +#else + #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) + #define MAX_HALFBUSY_WAIT_TIME 4 + double destTime = GetTime() + ms/1000; + if (ms > MAX_HALFBUSY_WAIT_TIME) ms -= MAX_HALFBUSY_WAIT_TIME; + #endif + + #if defined(_WIN32) + Sleep((unsigned int)ms); + #endif + #if defined(__linux__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) + struct timespec req = { 0 }; + time_t sec = (int)(ms/1000.0f); + ms -= (sec*1000); + req.tv_sec = sec; + req.tv_nsec = ms*1000000L; + + // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. + while (nanosleep(&req, &req) == -1) continue; + #endif + #if defined(__APPLE__) + usleep(ms*1000.0f); + #endif + + #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) + while (GetTime() < destTime) { } + #endif +#endif +} + +// Poll (store) all input events +static void PollInputEvents(void) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is just called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + +#if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = -1; + CORE.Input.Gamepad.axisCount = 0; +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + // Register previous keys states + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + PollKeyboardEvents(); + + // Register previous mouse states + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = 0.0f; + for (int i = 0; i < 3; i++) + { + CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i]; + } + + // Register gamepads buttons events + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + } + } +#endif + +#if defined(PLATFORM_UWP) + // Register previous keys states + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) + { + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + } + } + + // Register previous mouse states + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = 0.0f; + + for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; +#endif // PLATFORM_UWP + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) + + // Register previous keys states + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + // Register previous mouse states + for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Register previous mouse wheel state + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = 0.0f; +#endif + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + +#if defined(PLATFORM_DESKTOP) + // Check if gamepads are ready + // NOTE: We do it here in case of disconnection + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true; + else CORE.Input.Gamepad.ready[i] = false; + } + + // Register gamepads buttons events + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + + // Get current gamepad state + // NOTE: There is no callback available, so we get it manually + // Get remapped buttons + GLFWgamepadstate state = { 0 }; + glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller + + const unsigned char *buttons = state.buttons; + + for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++) + { + GamepadButton button = -1; + + switch (k) + { + case GLFW_GAMEPAD_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + + case GLFW_GAMEPAD_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case GLFW_GAMEPAD_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; + case GLFW_GAMEPAD_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + + case GLFW_GAMEPAD_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + default: break; + } + + if (button != -1) // Check for valid button + { + if (buttons[k] == GLFW_PRESS) + { + CORE.Input.Gamepad.currentState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentState[i][button] = 0; + } + } + + // Get current axis state + const float *axes = state.axes; + + for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++) + { + CORE.Input.Gamepad.axisState[i][k] = axes[k]; + } + + // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) + CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); + CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); + + CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST + 1; + } + } + + CORE.Window.resizedLastFrame = false; + +#if defined(SUPPORT_EVENTS_WAITING) + glfwWaitEvents(); +#else + glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events! +#endif +#endif // PLATFORM_DESKTOP + +// Gamepad support using emscripten API +// NOTE: GLFW3 joystick functionality not available in web +#if defined(PLATFORM_WEB) + // Get number of gamepads connected + int numGamepads = 0; + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); + + for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) + { + // Register previous gamepad button states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + + EmscriptenGamepadEvent gamepadState; + + int result = emscripten_get_gamepad_status(i, &gamepadState); + + if (result == EMSCRIPTEN_RESULT_SUCCESS) + { + // Register buttons data for every connected gamepad + for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) + { + GamepadButton button = -1; + + // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface + switch (j) + { + case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; + case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; + case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + default: break; + } + + if (button != -1) // Check for valid button + { + if (gamepadState.digitalButton[j] == 1) + { + CORE.Input.Gamepad.currentState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentState[i][button] = 0; + } + + //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + } + + // Register axis data for every connected gamepad + for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++) + { + CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; + } + + CORE.Input.Gamepad.axisCount = gamepadState.numAxes; + } + } +#endif + +#if defined(PLATFORM_ANDROID) + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < 260; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + // Android ALooper_pollAll() variables + int pollResult = 0; + int pollEvents = 0; + + // Poll Events (registered events) + // NOTE: Activity is paused if not enabled (CORE.Android.appEnabled) + while ((pollResult = ALooper_pollAll(CORE.Android.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) + { + // Process this event + if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); + + // NOTE: Never close window, native activity is controlled by the system! + if (CORE.Android.app->destroyRequested != 0) + { + //CORE.Window.shouldClose = true; + //ANativeActivity_finish(CORE.Android.app->activity); + } + } +#endif + +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI) + // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin, + // we now use both methods inside here. 2nd method is still used for legacy purposes (Allows for input trough SSH console) + ProcessKeyboard(); + + // NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread() + // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() +#endif +} + +// Copy back buffer to front buffers +static void SwapBuffers(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + glfwSwapBuffers(CORE.Window.handle); +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + eglSwapBuffers(CORE.Window.device, CORE.Window.surface); + +#if defined(PLATFORM_DRM) + if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); + abort(); + } + + struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface); + if (!bo) + { + TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); + abort(); + } + + uint32_t fb = 0; + int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, + CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); + abort(); + } + + result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, + &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); + abort(); + } + + if (CORE.Window.prevFB) + { + result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); + abort(); + } + } + CORE.Window.prevFB = fb; + + if (CORE.Window.prevBO) + { + gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); + } + CORE.Window.prevBO = bo; +#endif // PLATFORM_DRM +#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP +} + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) +// GLFW3 Error Callback, runs on GLFW3 error +static void ErrorCallback(int error, const char *description) +{ + TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); +} + + +// GLFW3 WindowSize Callback, runs when window is resizedLastFrame +// NOTE: Window resizing not allowed by default +static void WindowSizeCallback(GLFWwindow *window, int width, int height) +{ + SetupViewport(width, height); // Reset viewport and projection matrix for new size + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return; + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + // NOTE: Postprocessing texture is not scaled to new size + +} + +// GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowIconifyCallback(GLFWwindow *window, int iconified) +{ + if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified + else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored +} + +#if !defined(PLATFORM_WEB) +// GLFW3 WindowMaximize Callback, runs when window is maximized/restored +static void WindowMaximizeCallback(GLFWwindow *window, int maximized) +{ + if (maximized) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized + else CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored +} +#endif + +// GLFW3 WindowFocus Callback, runs when window get/lose focus +static void WindowFocusCallback(GLFWwindow *window, int focused) +{ + if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused + else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus +} + +// GLFW3 Keyboard Callback, runs on key pressed +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + //TRACELOG(LOG_DEBUG, "Key Callback: KEY:%i(%c) - SCANCODE:%i (STATE:%i)", key, key, scancode, action); + + if (key == CORE.Input.Keyboard.exitKey && action == GLFW_PRESS) + { + glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE); + + // NOTE: Before closing window, while loop must be left! + } +#if defined(SUPPORT_SCREEN_CAPTURE) + else if (key == GLFW_KEY_F12 && action == GLFW_PRESS) + { +#if defined(SUPPORT_GIF_RECORDING) + if (mods == GLFW_MOD_CONTROL) + { + if (gifRecording) + { + gifRecording = false; + + MsfGifResult result = msf_gif_end(&gifState); + + char path[512] = { 0 }; + #if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); + #else + strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); + #endif + + SaveFileData(path, result.data, (unsigned int)result.dataSize); + msf_gif_free(result); + + #if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1))); + #endif + + TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); + } + else + { + gifRecording = true; + gifFramesCounter = 0; + + msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height); + screenshotCounter++; + + TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); + } + } + else +#endif // SUPPORT_GIF_RECORDING + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + } +#endif // SUPPORT_SCREEN_CAPTURE + else + { + // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 + // to work properly with our implementation (IsKeyDown/IsKeyUp checks) + if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; + else CORE.Input.Keyboard.currentKeyState[key] = 1; + + // Check if there is space available in the key queue + if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } +} + +// GLFW3 Char Key Callback, runs on key down (gets equivalent unicode char value) +static void CharCallback(GLFWwindow *window, unsigned int key) +{ + //TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key); + + // NOTE: Registers any key down considering OS keyboard layout but + // do not detects action events, those should be managed by user... + // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 + // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char + + // Check if there is space available in the queue + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_KEY_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key; + CORE.Input.Keyboard.charPressedQueueCount++; + } +} + +// GLFW3 Mouse Button Callback, runs on mouse button pressed +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) +{ + // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, + // but future releases may add more actions (i.e. GLFW_REPEAT) + CORE.Input.Mouse.currentButtonState[button] = action; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_UP; + + // NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback() + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +// GLFW3 Cursor Position Callback, runs on mouse move +static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) +{ + CORE.Input.Mouse.position.x = (float)x; + CORE.Input.Mouse.position.y = (float)y; + CORE.Input.Touch.position[0] = CORE.Input.Mouse.position; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_MOVE; + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +// GLFW3 Srolling Callback, runs on mouse wheel +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) +{ + CORE.Input.Mouse.currentWheelMove = (float)yoffset; +} + +// GLFW3 CursorEnter Callback, when cursor enters the window +static void CursorEnterCallback(GLFWwindow *window, int enter) +{ + if (enter == true) CORE.Input.Mouse.cursorOnScreen = true; + else CORE.Input.Mouse.cursorOnScreen = false; +} + +// GLFW3 Window Drop Callback, runs when drop files into window +// NOTE: Paths are stored in dynamic memory for further retrieval +// Everytime new files are dropped, old ones are discarded +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) +{ + ClearDroppedFiles(); + + CORE.Window.dropFilesPath = (char **)RL_MALLOC(sizeof(char *)*count); + + for (int i = 0; i < count; i++) + { + CORE.Window.dropFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); + strcpy(CORE.Window.dropFilesPath[i], paths[i]); + } + + CORE.Window.dropFilesCount = count; +} +#endif + +#if defined(PLATFORM_ANDROID) +// ANDROID: Process activity lifecycle commands +static void AndroidCommandCallback(struct android_app *app, int32_t cmd) +{ + switch (cmd) + { + case APP_CMD_START: + { + //rendering = true; + } break; + case APP_CMD_RESUME: break; + case APP_CMD_INIT_WINDOW: + { + if (app->window != NULL) + { + if (CORE.Android.contextRebindRequired) + { + // Reset screen scaling to full display size + EGLint displayFormat; + eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); + ANativeWindow_setBuffersGeometry(app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); + + // Recreate display surface and re-attach OpenGL context + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, app->window, NULL); + eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context); + + CORE.Android.contextRebindRequired = false; + } + else + { + CORE.Window.display.width = ANativeWindow_getWidth(CORE.Android.app->window); + CORE.Window.display.height = ANativeWindow_getHeight(CORE.Android.app->window); + + // Init graphics device (display device and OpenGL context) + InitGraphicsDevice(CORE.Window.screen.width, CORE.Window.screen.height); + + // Init hi-res timer + InitTimer(); + + #if defined(SUPPORT_DEFAULT_FONT) + // Load default font + // NOTE: External function (defined in module: text) + LoadFontDefault(); + Rectangle rec = GetFontDefault().recs[95]; + // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + #endif + + // TODO: GPU assets reload in case of lost focus (lost context) + // NOTE: This problem has been solved just unbinding and rebinding context from display + /* + if (assetsReloadRequired) + { + for (int i = 0; i < assetsCount; i++) + { + // TODO: Unload old asset if required + + // Load texture again to pointed texture + (*textureAsset + i) = LoadTexture(assetPath[i]); + } + } + */ + } + } + } break; + case APP_CMD_GAINED_FOCUS: + { + CORE.Android.appEnabled = true; + //ResumeMusicStream(); + } break; + case APP_CMD_PAUSE: break; + case APP_CMD_LOST_FOCUS: + { + CORE.Android.appEnabled = false; + //PauseMusicStream(); + } break; + case APP_CMD_TERM_WINDOW: + { + // Dettach OpenGL context and destroy display surface + // NOTE 1: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) + // NOTE 2: In some cases (too many context loaded), OS could unload context automatically... :( + eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(CORE.Window.device, CORE.Window.surface); + + CORE.Android.contextRebindRequired = true; + } break; + case APP_CMD_SAVE_STATE: break; + case APP_CMD_STOP: break; + case APP_CMD_DESTROY: + { + // TODO: Finish activity? + //ANativeActivity_finish(CORE.Android.app->activity); + } break; + case APP_CMD_CONFIG_CHANGED: + { + //AConfiguration_fromAssetManager(CORE.Android.app->config, CORE.Android.app->activity->assetManager); + //print_cur_config(CORE.Android.app); + + // Check screen orientation here! + } break; + default: break; + } +} + +// ANDROID: Get input events +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) +{ + // If additional inputs are required check: + // https://developer.android.com/ndk/reference/group/input + // https://developer.android.com/training/game-controllers/controller-input + + int type = AInputEvent_getType(event); + int source = AInputEvent_getSource(event); + + if (type == AINPUT_EVENT_TYPE_MOTION) + { + if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || + ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) + { + // Get first touch position + CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0); + CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0); + + // Get second touch position + CORE.Input.Touch.position[1].x = AMotionEvent_getX(event, 1); + CORE.Input.Touch.position[1].y = AMotionEvent_getY(event, 1); + + int32_t keycode = AKeyEvent_getKeyCode(event); + if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) + { + CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up + + // Stop processing gamepad buttons + return 1; + } + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + int32_t keycode = AKeyEvent_getKeyCode(event); + //int32_t AKeyEvent_getMetaState(event); + + // Save current button and its state + // NOTE: Android key action is 0 for down and 1 for up + if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) + { + CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up + + if (keycode == AKEYCODE_POWER) + { + // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS + // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS + // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. + // NOTE: AndroidManifest.xml must have + // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour + return 0; + } + else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) + { + // Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS! + return 1; + } + else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) + { + // Set default OS behaviour + return 0; + } + + return 0; + } + + CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0); + CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0); + + int32_t action = AMotionEvent_getAction(event); + unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + + if (flags == AMOTION_EVENT_ACTION_DOWN || flags == AMOTION_EVENT_ACTION_MOVE) + { + CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 1; + } + else if (flags == AMOTION_EVENT_ACTION_UP) + { + CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 0; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) + + GestureEvent gestureEvent; + + // Register touch actions + if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_DOWN; + else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_UP; + else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_MOVE; + + // Register touch points count + // NOTE: Documentation says pointerCount is Always >= 1, + // but in practice it can be 0 or over a million + gestureEvent.pointCount = AMotionEvent_getPointerCount(event); + + // Only enable gestures for 1-3 touch points + if ((gestureEvent.pointCount > 0) && (gestureEvent.pointCount < 4)) + { + // Register touch points id + // NOTE: Only two points registered + gestureEvent.pointerId[0] = AMotionEvent_getPointerId(event, 0); + gestureEvent.pointerId[1] = AMotionEvent_getPointerId(event, 1); + + // Register touch points position + gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) }; + gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) }; + + // Normalize gestureEvent.position[x] for screenWidth and screenHeight + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + gestureEvent.position[1].x /= (float)GetScreenWidth(); + gestureEvent.position[1].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); + } +#endif + + return 0; +} +#endif + +#if defined(PLATFORM_WEB) +// Register touch input events +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + for (int i = 0; i < touchEvent->numTouches; i++) + { + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE; + + // Register touch points count + gestureEvent.pointCount = touchEvent->numTouches; + + // Register touch points id + gestureEvent.pointerId[0] = touchEvent->touches[0].identifier; + gestureEvent.pointerId[1] = touchEvent->touches[1].identifier; + + // Register touch points position + // NOTE: Only two points registered + gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; + gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY }; + + double canvasWidth, canvasHeight; + // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but + // we are looking for actual CSS size: canvas.style.width and canvas.style.height + //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); + emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); + + // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth); + gestureEvent.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight); + gestureEvent.position[1].x *= ((float)GetScreenWidth()/(float)canvasWidth); + gestureEvent.position[1].y *= ((float)GetScreenHeight()/(float)canvasHeight); + + CORE.Input.Touch.position[0] = gestureEvent.position[0]; + CORE.Input.Touch.position[1] = gestureEvent.position[1]; + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#else + // Support only simple touch position + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + { + // Get first touch position + CORE.Input.Touch.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; + + double canvasWidth, canvasHeight; + //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); + emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); + + // Normalize gestureEvent.position[x] for screenWidth and screenHeight + CORE.Input.Touch.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth); + CORE.Input.Touch.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight); + } +#endif + + return 1; +} + +// Register connected/disconnected gamepads events +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + /* + TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", + gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); + + for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + */ + + if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) CORE.Input.Gamepad.ready[gamepadEvent->index] = true; + else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; + + // TODO: Test gamepadEvent->index + + return 0; +} +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + +#if defined(SUPPORT_SSH_KEYBOARD_RPI) +// Initialize Keyboard system (using standard input) +static void InitKeyboard(void) +{ + // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor + + // Make stdin non-blocking (not enough, need to configure to non-canonical mode) + int flags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags + fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified + + // Save terminal keyboard settings and reconfigure terminal with new settings + struct termios keyboardNewSettings; + tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings + keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; + + // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing + // NOTE: ISIG controls if ^C and ^Z generate break signals or not + keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); + //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); + keyboardNewSettings.c_cc[VMIN] = 1; + keyboardNewSettings.c_cc[VTIME] = 0; + + // Set new keyboard settings (change occurs immediately) + tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); + + // NOTE: Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE + + // Save old keyboard mode to restore it at the end + if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0) + { + // NOTE: It could mean we are using a remote keyboard through ssh! + TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (SSH keyboard?)"); + } + else + { + // We reconfigure keyboard mode to get: + // - scancodes (K_RAW) + // - keycodes (K_MEDIUMRAW) + // - ASCII chars (K_XLATE) + // - UNICODE chars (K_UNICODE) + ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); + } + + // Register keyboard restore when program finishes + atexit(RestoreKeyboard); +} + +// Process keyboard inputs +// TODO: Most probably input reading and processing should be in a separate thread +static void ProcessKeyboard(void) +{ + #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read + + // Keyboard input polling (fill keys[256] array with status) + int bufferByteCount = 0; // Bytes available on the buffer + char keysBuffer[MAX_KEYBUFFER_SIZE]; // Max keys to be read at a time + + // Read availables keycodes from stdin + bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call + + // Reset pressed keys array (it will be filled below) + if (bufferByteCount > 0) for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; + + // Check keys from event input workers (This is the new keyboard reading method) + //for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = CORE.Input.Keyboard.currentKeyStateEvdev[i]; + + // Fill all read bytes (looking for keys) + for (int i = 0; i < bufferByteCount; i++) + { + // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! + // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 + if (keysBuffer[i] == 0x1b) + { + // Detect ESC to stop program + if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; + else + { + if (keysBuffer[i + 1] == 0x5b) // Special function key + { + if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) + { + // Process special function keys (F1 - F12) + switch (keysBuffer[i + 3]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 + case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 + case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 + case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 + case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 + case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 + case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 + case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 + case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 + case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 + case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 + case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 + default: break; + } + + if (keysBuffer[i + 2] == 0x5b) i += 4; + else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; + } + else + { + switch (keysBuffer[i + 2]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP + case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN + case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT + case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT + default: break; + } + + i += 3; // Jump to next key + } + + // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) + } + } + } + else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with KEY_*) + { + CORE.Input.Keyboard.currentKeyState[257] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE + { + CORE.Input.Keyboard.currentKeyState[259] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else + { + // Translate lowercase a-z letters to A-Z + if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) + { + CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; + } + else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } + + // Check exit key (same functionality as GLFW3 KeyCallback()) + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + +#if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (CORE.Input.Keyboard.currentKeyState[301] == 1) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } +#endif +} + +// Restore default keyboard input +static void RestoreKeyboard(void) +{ + // Reset to default keyboard settings + tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); + + // Reconfigure keyboard to default mode + ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); +} +#endif // SUPPORT_SSH_KEYBOARD_RPI + +// Initialise user input from evdev(/dev/input/event) this means mouse, keyboard or gamepad devices +static void InitEvdevInput(void) +{ + char path[MAX_FILEPATH_LENGTH]; + DIR *directory; + struct dirent *entity; + + // Initialise keyboard file descriptor + CORE.Input.Keyboard.fd = -1; + + // Reset variables + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + CORE.Input.Touch.position[i].x = -1; + CORE.Input.Touch.position[i].y = -1; + } + + // Reset keyboard key state + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; + + // Open the linux directory of "/dev/input" + directory = opendir(DEFAULT_EVDEV_PATH); + + if (directory) + { + while ((entity = readdir(directory)) != NULL) + { + if (strncmp("event", entity->d_name, strlen("event")) == 0) // Search for devices named "event*" + { + sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); + ConfigureEvdevDevice(path); // Configure the device if appropriate + } + } + + closedir(directory); + } + else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); +} + +// Identifies a input device and configures it for use if appropriate +static void ConfigureEvdevDevice(char *device) +{ + #define BITS_PER_LONG (8*sizeof(long)) + #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) + #define OFF(x) ((x)%BITS_PER_LONG) + #define BIT(x) (1UL<> OFF(bit)) & 1) + + struct input_absinfo absinfo; + unsigned long evBits[NBITS(EV_MAX)]; + unsigned long absBits[NBITS(ABS_MAX)]; + unsigned long relBits[NBITS(REL_MAX)]; + unsigned long keyBits[NBITS(KEY_MAX)]; + bool hasAbs = false; + bool hasRel = false; + bool hasAbsMulti = false; + int freeWorkerId = -1; + int fd = -1; + + InputEventWorker *worker; + + // Open the device and allocate worker + //------------------------------------------------------------------------------------------------------- + // Find a free spot in the workers array + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].threadId == 0) + { + freeWorkerId = i; + break; + } + } + + // Select the free worker from array + if (freeWorkerId >= 0) + { + worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker + memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker + } + else + { + TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device); + return; + } + + // Open the device + fd = open(device, O_RDONLY | O_NONBLOCK); + if (fd < 0) + { + TRACELOG(LOG_WARNING, "RPI: Failed to open input device %s", device); + return; + } + worker->fd = fd; + + // Grab number on the end of the devices name "event" + int devNum = 0; + char *ptrDevName = strrchr(device, 't'); + worker->eventNum = -1; + + if (ptrDevName != NULL) + { + if (sscanf(ptrDevName, "t%d", &devNum) == 1) + worker->eventNum = devNum; + } + + // At this point we have a connection to the device, but we don't yet know what the device is. + // It could be many things, even as simple as a power button... + //------------------------------------------------------------------------------------------------------- + + // Identify the device + //------------------------------------------------------------------------------------------------------- + ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties + + // Check for absolute input devices + if (TEST_BIT(evBits, EV_ABS)) + { + ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); + + // Check for absolute movement support (usualy touchscreens, but also joysticks) + if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) + { + hasAbs = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + + // Check for multiple absolute movement support (usualy multitouch touchscreens) + if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) + { + hasAbsMulti = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + } + + // Check for relative movement support (usualy mouse) + if (TEST_BIT(evBits, EV_REL)) + { + ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); + + if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; + } + + // Check for button support to determine the device type(usualy on all input devices) + if (TEST_BIT(evBits, EV_KEY)) + { + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); + + if (hasAbs || hasAbsMulti) + { + if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen + if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet + if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device + } + + if (hasRel) + { + if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse + if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse + } + + if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + + if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard + } + //------------------------------------------------------------------------------------------------------- + + // Decide what to do with the device + //------------------------------------------------------------------------------------------------------- + if (worker->isKeyboard && CORE.Input.Keyboard.fd == -1) + { + // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a + // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate + // threads so that they don't drop events when the frame rate is slow. + TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device); + CORE.Input.Keyboard.fd = worker->fd; + } + else if (worker->isTouch || worker->isMouse) + { + // Looks like an interesting device + TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device, + worker->isMouse? "mouse " : "", + worker->isMultitouch? "multitouch " : "", + worker->isTouch? "touchscreen " : "", + worker->isGamepad? "gamepad " : ""); + + // Create a thread for this device + int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); + if (error != 0) + { + TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error); + worker->threadId = 0; + close(fd); + } + +#if defined(USE_LAST_TOUCH_DEVICE) + // Find touchscreen with the highest index + int maxTouchNumber = -1; + + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; + } + + // Find touchscreens with lower indexes + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) + { + if (CORE.Input.eventWorker[i].threadId != 0) + { + TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i); + pthread_cancel(CORE.Input.eventWorker[i].threadId); + close(CORE.Input.eventWorker[i].fd); + } + } + } +#endif + } + else close(fd); // We are not interested in this device + //------------------------------------------------------------------------------------------------------- +} + +static void PollKeyboardEvents(void) +{ + // Scancode to keycode mapping for US keyboards + // TODO: Probably replace this with a keymap from the X11 to get the correct regional map for the keyboard: + // Currently non US keyboards will have the wrong mapping for some keys + static const int keymap_US[] = + { 0,256,49,50,51,52,53,54,55,56,57,48,45,61,259,258,81,87,69,82,84, + 89,85,73,79,80,91,93,257,341,65,83,68,70,71,72,74,75,76,59,39,96, + 340,92,90,88,67,86,66,78,77,44,46,47,344,332,342,32,280,290,291, + 292,293,294,295,296,297,298,299,282,281,327,328,329,333,324,325, + 326,334,321,322,323,320,330,0,85,86,300,301,89,90,91,92,93,94,95, + 335,345,331,283,346,101,268,265,266,263,262,269,264,267,260,261, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,347,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,0,0,0,0,0,200,201,202,203,204,205,206,207,208,209,210, + 211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226, + 227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242, + 243,244,245,246,247,248,0,0,0,0,0,0,0, }; + + int fd = CORE.Input.Keyboard.fd; + if (fd == -1) return; + + struct input_event event; + int keycode; + + // Try to read data from the keyboard and only continue if successful + while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Button parsing + if (event.type == EV_KEY) + { + // Keyboard button parsing + if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 + { + keycode = keymap_US[event.code & 0xFF]; // The code we get is a scancode so we look up the apropriate keycode + + // Make sure we got a valid keycode + if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) + { + // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt + // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, + // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat + CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; + if (event.value >= 1) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + #if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (CORE.Input.Keyboard.currentKeyState[301] == 1) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + #endif + + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + + TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); + } + } + } + } +} + +// Input device events reading thread +static void *EventThread(void *arg) +{ + struct input_event event; + InputEventWorker *worker = (InputEventWorker *)arg; + + int touchAction = -1; + bool gestureUpdate = false; + + while (!CORE.Window.shouldClose) + { + // Try to read data from the device and only continue if successful + while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Relative movement parsing + if (event.type == EV_REL) + { + if (event.code == REL_X) + { + CORE.Input.Mouse.position.x += event.value; + CORE.Input.Touch.position[0].x = CORE.Input.Mouse.position.x; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + if (event.code == REL_Y) + { + CORE.Input.Mouse.position.y += event.value; + CORE.Input.Touch.position[0].y = CORE.Input.Mouse.position.y; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + if (event.code == REL_WHEEL) CORE.Input.Mouse.currentWheelMove += event.value; + } + + // Absolute movement parsing + if (event.type == EV_ABS) + { + // Basic movement + if (event.code == ABS_X) + { + CORE.Input.Mouse.position.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + if (event.code == ABS_Y) + { + CORE.Input.Mouse.position.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + // Multitouch movement + if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events + + if (event.code == ABS_MT_POSITION_X) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + } + + if (event.code == ABS_MT_POSITION_Y) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + } + + if (event.code == ABS_MT_TRACKING_ID) + { + if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) + { + // Touch has ended for this point + CORE.Input.Touch.position[worker->touchSlot].x = -1; + CORE.Input.Touch.position[worker->touchSlot].y = -1; + } + } + + // Touchscreen tap + if (event.code == ABS_PRESSURE) + { + int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON]; + + if (!event.value && previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = 0; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_UP; + gestureUpdate = true; + #endif + } + + if (event.value && !previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = 1; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_DOWN; + gestureUpdate = true; + #endif + } + } + + } + + // Button parsing + if (event.type == EV_KEY) + { + // Mouse button parsing + if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = event.value; + + #if defined(SUPPORT_GESTURES_SYSTEM) + if (event.value > 0) touchAction = TOUCH_DOWN; + else touchAction = TOUCH_UP; + gestureUpdate = true; + #endif + } + + if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_RIGHT_BUTTON] = event.value; + if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_MIDDLE_BUTTON] = event.value; + } + + // Screen confinement + if (!CORE.Input.Mouse.cursorHidden) + { + if (CORE.Input.Mouse.position.x < 0) CORE.Input.Mouse.position.x = 0; + if (CORE.Input.Mouse.position.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.position.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; + + if (CORE.Input.Mouse.position.y < 0) CORE.Input.Mouse.position.y = 0; + if (CORE.Input.Mouse.position.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.position.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; + } + + // Gesture update + if (gestureUpdate) + { + #if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + gestureEvent.pointCount = 0; + gestureEvent.touchAction = touchAction; + + if (CORE.Input.Touch.position[0].x >= 0) gestureEvent.pointCount++; + if (CORE.Input.Touch.position[1].x >= 0) gestureEvent.pointCount++; + if (CORE.Input.Touch.position[2].x >= 0) gestureEvent.pointCount++; + if (CORE.Input.Touch.position[3].x >= 0) gestureEvent.pointCount++; + + gestureEvent.pointerId[0] = 0; + gestureEvent.pointerId[1] = 1; + gestureEvent.pointerId[2] = 2; + gestureEvent.pointerId[3] = 3; + + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + gestureEvent.position[1] = CORE.Input.Touch.position[1]; + gestureEvent.position[2] = CORE.Input.Touch.position[2]; + gestureEvent.position[3] = CORE.Input.Touch.position[3]; + + ProcessGestureEvent(gestureEvent); + #endif + } + } + Wait(5); // Sleep for 5ms to avoid hogging CPU time + } + + close(worker->fd); + + return NULL; +} + +// Init gamepad system +static void InitGamepad(void) +{ + char gamepadDev[128] = ""; + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); + + if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY|O_NONBLOCK)) < 0) + { + // NOTE: Only show message for first gamepad + if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available"); + } + else + { + CORE.Input.Gamepad.ready[i] = true; + + // NOTE: Only create one thread + if (i == 0) + { + int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL); + + if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread"); + else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully"); + } + } + } +} + +// Process Gamepad (/dev/input/js0) +static void *GamepadThread(void *arg) +{ + #define JS_EVENT_BUTTON 0x01 // Button pressed/released + #define JS_EVENT_AXIS 0x02 // Joystick axis moved + #define JS_EVENT_INIT 0x80 // Initial state of device + + struct js_event { + unsigned int time; // event timestamp in milliseconds + short value; // event value + unsigned char type; // event type + unsigned char number; // event axis/button number + }; + + // Read gamepad event + struct js_event gamepadEvent; + + while (!CORE.Window.shouldClose) + { + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) + { + gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events + + // Process gamepad events by type + if (gamepadEvent.type == JS_EVENT_BUTTON) + { + //TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + + if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) + { + // 1 - button pressed, 0 - button released + CORE.Input.Gamepad.currentState[i][gamepadEvent.number] = (int)gamepadEvent.value; + + if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number; + else CORE.Input.Gamepad.lastButtonPressed = -1; + } + } + else if (gamepadEvent.type == JS_EVENT_AXIS) + { + //TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + + if (gamepadEvent.number < MAX_GAMEPAD_AXIS) + { + // NOTE: Scaling of gamepadEvent.value to get values between -1..1 + CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; + } + } + } + else Wait(1); // Sleep for 1 ms to avoid hogging CPU time + } + } + + return NULL; +} +#endif // PLATFORM_RPI || PLATFORM_DRM + +#if defined(PLATFORM_UWP) +// UWP function pointers +// NOTE: Those pointers are set by UWP App +static UWPQueryTimeFunc uwpQueryTimeFunc = NULL; +static UWPSleepFunc uwpSleepFunc = NULL; +static UWPDisplaySizeFunc uwpDisplaySizeFunc = NULL; +static UWPMouseFunc uwpMouseLockFunc = NULL; +static UWPMouseFunc uwpMouseUnlockFunc = NULL; +static UWPMouseFunc uwpMouseShowFunc = NULL; +static UWPMouseFunc uwpMouseHideFunc = NULL; +static UWPMouseSetPosFunc uwpMouseSetPosFunc = NULL; +static void *uwpCoreWindow = NULL; + +// Check all required UWP function pointers have been set +bool UWPIsConfigured() +{ + bool pass = true; + + if (uwpQueryTimeFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetQueryTimeFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpSleepFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetSleepFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpDisplaySizeFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetDisplaySizeFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseLockFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseLockFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseUnlockFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseUnlockFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseShowFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseShowFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseHideFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseHideFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseSetPosFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseSetPosFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpCoreWindow == NULL) { TRACELOG(LOG_ERROR, "UWP: A pointer to the UWP core window must be set before InitWindow()"); pass = false; } + + return pass; +} + +// UWP function handlers get/set +void UWPSetDataPath(const char* path) { CORE.UWP.internalDataPath = path; } +UWPQueryTimeFunc UWPGetQueryTimeFunc(void) { return uwpQueryTimeFunc; } +void UWPSetQueryTimeFunc(UWPQueryTimeFunc func) { uwpQueryTimeFunc = func; } +UWPSleepFunc UWPGetSleepFunc(void) { return uwpSleepFunc; } +void UWPSetSleepFunc(UWPSleepFunc func) { uwpSleepFunc = func; } +UWPDisplaySizeFunc UWPGetDisplaySizeFunc(void) { return uwpDisplaySizeFunc; } +void UWPSetDisplaySizeFunc(UWPDisplaySizeFunc func) { uwpDisplaySizeFunc = func; } +UWPMouseFunc UWPGetMouseLockFunc() { return uwpMouseLockFunc; } +void UWPSetMouseLockFunc(UWPMouseFunc func) { uwpMouseLockFunc = func; } +UWPMouseFunc UWPGetMouseUnlockFunc() { return uwpMouseUnlockFunc; } +void UWPSetMouseUnlockFunc(UWPMouseFunc func) { uwpMouseUnlockFunc = func; } +UWPMouseFunc UWPGetMouseShowFunc() { return uwpMouseShowFunc; } +void UWPSetMouseShowFunc(UWPMouseFunc func) { uwpMouseShowFunc = func; } +UWPMouseFunc UWPGetMouseHideFunc() { return uwpMouseHideFunc; } +void UWPSetMouseHideFunc(UWPMouseFunc func) { uwpMouseHideFunc = func; } +UWPMouseSetPosFunc UWPGetMouseSetPosFunc() { return uwpMouseSetPosFunc; } +void UWPSetMouseSetPosFunc(UWPMouseSetPosFunc func) { uwpMouseSetPosFunc = func; } + +void *UWPGetCoreWindowPtr() { return uwpCoreWindow; } +void UWPSetCoreWindowPtr(void* ptr) { uwpCoreWindow = ptr; } +void UWPMouseWheelEvent(int deltaY) { CORE.Input.Mouse.currentWheelMove = (float)deltaY; } + +void UWPKeyDownEvent(int key, bool down, bool controlKey) +{ + if (key == CORE.Input.Keyboard.exitKey && down) + { + // Time to close the window. + CORE.Window.shouldClose = true; + } +#if defined(SUPPORT_SCREEN_CAPTURE) + else if (key == KEY_F12 && down) + { +#if defined(SUPPORT_GIF_RECORDING) + if (controlKey) + { + if (gifRecording) + { + gifRecording = false; + + MsfGifResult result = msf_gif_end(&gifState); + + SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.UWP.internalDataPath, screenshotCounter), result.data, result.dataSize); + msf_gif_free(result); + +#if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1))); +#endif + TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); + } + else + { + gifRecording = true; + gifFramesCounter = 0; + + msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height); + screenshotCounter++; + + TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); + } + } + else +#endif // SUPPORT_GIF_RECORDING + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + } +#endif // SUPPORT_SCREEN_CAPTURE + else + { + CORE.Input.Keyboard.currentKeyState[key] = down; + } +} + +void UWPKeyCharEvent(int key) +{ + if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } +} + +void UWPMouseButtonEvent(int button, bool down) +{ + CORE.Input.Mouse.currentButtonState[button] = down; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_UP; + + // NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback() + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +void UWPMousePosEvent(double x, double y) +{ + CORE.Input.Mouse.position.x = (float)x; + CORE.Input.Mouse.position.y = (float)y; + CORE.Input.Touch.position[0] = CORE.Input.Mouse.position; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_MOVE; + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Mouse.position; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +void UWPResizeEvent(int width, int height) +{ + SetupViewport(width, height); // Reset viewport and projection matrix for new size + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + + // NOTE: Postprocessing texture is not scaled to new size + + CORE.Window.resizedLastFrame = true; +} + +void UWPActivateGamepadEvent(int gamepad, bool active) +{ + if (gamepad < MAX_GAMEPADS) CORE.Input.Gamepad.ready[gamepad] = active; +} + +void UWPRegisterGamepadButton(int gamepad, int button, bool down) +{ + if (gamepad < MAX_GAMEPADS) + { + if (button < MAX_GAMEPAD_BUTTONS) + { + CORE.Input.Gamepad.currentState[gamepad][button] = down; + CORE.Input.Gamepad.lastButtonPressed = button; + } + } +} + +void UWPRegisterGamepadAxis(int gamepad, int axis, float value) +{ + if (gamepad < MAX_GAMEPADS) + { + if (axis < MAX_GAMEPAD_AXIS) CORE.Input.Gamepad.axisState[gamepad][axis] = value; + } +} + +void UWPGestureMove(int pointer, float x, float y) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + // Assign the pointer ID and touch action + gestureEvent.pointerId[0] = pointer; + gestureEvent.touchAction = TOUCH_MOVE; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0].x = x; + gestureEvent.position[0].y = y; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +void UWPGestureTouch(int pointer, float x, float y, bool touch) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + // Assign the pointer ID and touch action + gestureEvent.pointerId[0] = pointer; + gestureEvent.touchAction = touch ? TOUCH_DOWN : TOUCH_UP; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0].x = x; + gestureEvent.position[0].y = y; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +#endif // PLATFORM_UWP + +#if defined(PLATFORM_DRM) +// Search matching DRM mode in connector's mode list +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) +{ + if (NULL == connector) return -1; + if (NULL == mode) return -1; + + // safe bitwise comparison of two modes + #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b)) + + for (size_t i = 0; i < connector->count_modes; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, + connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i; + } + + return -1; + + #undef BINCMP +} + +// Search exactly matching DRM connector mode in connector's list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); + + if (NULL == connector) return -1; + + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue; + + if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; + } + + TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); + return -1; +} + +// Search the nearest matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); + + if (NULL == connector) return -1; + + int nearestIndex = -1; + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->hdisplay < width) || (mode->vdisplay < height) | (mode->vrefresh < fps)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); + continue; + } + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); + continue; + } + + if ((mode->hdisplay >= width) && (mode->vdisplay >= height) && (mode->vrefresh >= fps)) + { + const int widthDiff = mode->hdisplay - width; + const int heightDiff = mode->vdisplay - height; + const int fpsDiff = mode->vrefresh - fps; + + if (nearestIndex < 0) + { + nearestIndex = i; + continue; + } + + const int nearestWidthDiff = CORE.Window.connector->modes[nearestIndex].hdisplay - width; + const int nearestHeightDiff = CORE.Window.connector->modes[nearestIndex].vdisplay - height; + const int nearestFpsDiff = CORE.Window.connector->modes[nearestIndex].vrefresh - fps; + + if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) nearestIndex = i; + } + } + + return nearestIndex; +} +#endif diff --git a/raylib/easings.h b/raylib/easings.h new file mode 100644 index 0000000..3441305 --- /dev/null +++ b/raylib/easings.h @@ -0,0 +1,263 @@ +/******************************************************************************************* +* +* raylib easings (header only file) +* +* Useful easing functions for values animation +* +* This header uses: +* #define EASINGS_STATIC_INLINE // Inlines all functions code, so it runs faster. +* // This requires lots of memory on system. +* How to use: +* The four inputs t,b,c,d are defined as follows: +* t = current time (in any unit measure, but same unit as duration) +* b = starting value to interpolate +* c = the total change in value of b that needs to occur +* d = total time it should take to complete (duration) +* +* Example: +* +* int currentTime = 0; +* int duration = 100; +* float startPositionX = 0.0f; +* float finalPositionX = 30.0f; +* float currentPositionX = startPositionX; +* +* while (currentPositionX < finalPositionX) +* { +* currentPositionX = EaseSineIn(currentTime, startPositionX, finalPositionX - startPositionX, duration); +* currentTime++; +* } +* +* A port of Robert Penner's easing equations to C (http://robertpenner.com/easing/) +* +* Robert Penner License +* --------------------------------------------------------------------------------- +* Open source under the BSD License. +* +* Copyright (c) 2001 Robert Penner. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* - Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* - Neither the name of the author nor the names of contributors may be used +* to endorse or promote products derived from this software without specific +* prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. +* --------------------------------------------------------------------------------- +* +* Copyright (c) 2015 Ramon Santamaria +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef EASINGS_H +#define EASINGS_H + +#define EASINGS_STATIC_INLINE // NOTE: By default, compile functions as static inline + +#if defined(EASINGS_STATIC_INLINE) + #define EASEDEF static inline +#else + #define EASEDEF extern +#endif + +#include // Required for: sinf(), cosf(), sqrtf(), powf() + +#ifndef PI + #define PI 3.14159265358979323846f //Required as PI is not always defined in math.h +#endif + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +// Linear Easing functions +EASEDEF float EaseLinearNone(float t, float b, float c, float d) { return (c*t/d + b); } +EASEDEF float EaseLinearIn(float t, float b, float c, float d) { return (c*t/d + b); } +EASEDEF float EaseLinearOut(float t, float b, float c, float d) { return (c*t/d + b); } +EASEDEF float EaseLinearInOut(float t,float b, float c, float d) { return (c*t/d + b); } + +// Sine Easing functions +EASEDEF float EaseSineIn(float t, float b, float c, float d) { return (-c*cosf(t/d*(PI/2.0f)) + c + b); } +EASEDEF float EaseSineOut(float t, float b, float c, float d) { return (c*sinf(t/d*(PI/2.0f)) + b); } +EASEDEF float EaseSineInOut(float t, float b, float c, float d) { return (-c/2.0f*(cosf(PI*t/d) - 1.0f) + b); } + +// Circular Easing functions +EASEDEF float EaseCircIn(float t, float b, float c, float d) { t /= d; return (-c*(sqrtf(1.0f - t*t) - 1.0f) + b); } +EASEDEF float EaseCircOut(float t, float b, float c, float d) { t = t/d - 1.0f; return (c*sqrtf(1.0f - t*t) + b); } +EASEDEF float EaseCircInOut(float t, float b, float c, float d) +{ + if ((t/=d/2.0f) < 1.0f) return (-c/2.0f*(sqrtf(1.0f - t*t) - 1.0f) + b); + t -= 2.0f; return (c/2.0f*(sqrtf(1.0f - t*t) + 1.0f) + b); +} + +// Cubic Easing functions +EASEDEF float EaseCubicIn(float t, float b, float c, float d) { t /= d; return (c*t*t*t + b); } +EASEDEF float EaseCubicOut(float t, float b, float c, float d) { t = t/d - 1.0f; return (c*(t*t*t + 1.0f) + b); } +EASEDEF float EaseCubicInOut(float t, float b, float c, float d) +{ + if ((t/=d/2.0f) < 1.0f) return (c/2.0f*t*t*t + b); + t -= 2.0f; return (c/2.0f*(t*t*t + 2.0f) + b); +} + +// Quadratic Easing functions +EASEDEF float EaseQuadIn(float t, float b, float c, float d) { t /= d; return (c*t*t + b); } +EASEDEF float EaseQuadOut(float t, float b, float c, float d) { t /= d; return (-c*t*(t - 2.0f) + b); } +EASEDEF float EaseQuadInOut(float t, float b, float c, float d) +{ + if ((t/=d/2) < 1) return (((c/2)*(t*t)) + b); + return (-c/2.0f*(((t - 1.0f)*(t - 3.0f)) - 1.0f) + b); +} + +// Exponential Easing functions +EASEDEF float EaseExpoIn(float t, float b, float c, float d) { return (t == 0.0f) ? b : (c*powf(2.0f, 10.0f*(t/d - 1.0f)) + b); } +EASEDEF float EaseExpoOut(float t, float b, float c, float d) { return (t == d) ? (b + c) : (c*(-powf(2.0f, -10.0f*t/d) + 1.0f) + b); } +EASEDEF float EaseExpoInOut(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if (t == d) return (b + c); + if ((t/=d/2.0f) < 1.0f) return (c/2.0f*powf(2.0f, 10.0f*(t - 1.0f)) + b); + + return (c/2.0f*(-powf(2.0f, -10.0f*(t - 1.0f)) + 2.0f) + b); +} + +// Back Easing functions +EASEDEF float EaseBackIn(float t, float b, float c, float d) +{ + float s = 1.70158f; + float postFix = t/=d; + return (c*(postFix)*t*((s + 1.0f)*t - s) + b); +} + +EASEDEF float EaseBackOut(float t, float b, float c, float d) +{ + float s = 1.70158f; + t = t/d - 1.0f; + return (c*(t*t*((s + 1.0f)*t + s) + 1.0f) + b); +} + +EASEDEF float EaseBackInOut(float t, float b, float c, float d) +{ + float s = 1.70158f; + if ((t/=d/2.0f) < 1.0f) + { + s *= 1.525f; + return (c/2.0f*(t*t*((s + 1.0f)*t - s)) + b); + } + + float postFix = t-=2.0f; + s *= 1.525f; + return (c/2.0f*((postFix)*t*((s + 1.0f)*t + s) + 2.0f) + b); +} + +// Bounce Easing functions +EASEDEF float EaseBounceOut(float t, float b, float c, float d) +{ + if ((t/=d) < (1.0f/2.75f)) + { + return (c*(7.5625f*t*t) + b); + } + else if (t < (2.0f/2.75f)) + { + float postFix = t-=(1.5f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.75f) + b); + } + else if (t < (2.5/2.75)) + { + float postFix = t-=(2.25f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.9375f) + b); + } + else + { + float postFix = t-=(2.625f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.984375f) + b); + } +} + +EASEDEF float EaseBounceIn(float t, float b, float c, float d) { return (c - EaseBounceOut(d - t, 0.0f, c, d) + b); } +EASEDEF float EaseBounceInOut(float t, float b, float c, float d) +{ + if (t < d/2.0f) return (EaseBounceIn(t*2.0f, 0.0f, c, d)*0.5f + b); + else return (EaseBounceOut(t*2.0f - d, 0.0f, c, d)*0.5f + c*0.5f + b); +} + +// Elastic Easing functions +EASEDEF float EaseElasticIn(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if ((t/=d) == 1.0f) return (b + c); + + float p = d*0.3f; + float a = c; + float s = p/4.0f; + float postFix = a*powf(2.0f, 10.0f*(t-=1.0f)); + + return (-(postFix*sinf((t*d-s)*(2.0f*PI)/p )) + b); +} + +EASEDEF float EaseElasticOut(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if ((t/=d) == 1.0f) return (b + c); + + float p = d*0.3f; + float a = c; + float s = p/4.0f; + + return (a*powf(2.0f,-10.0f*t)*sinf((t*d-s)*(2.0f*PI)/p) + c + b); +} + +EASEDEF float EaseElasticInOut(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if ((t/=d/2.0f) == 2.0f) return (b + c); + + float p = d*(0.3f*1.5f); + float a = c; + float s = p/4.0f; + + if (t < 1.0f) + { + float postFix = a*powf(2.0f, 10.0f*(t-=1.0f)); + return -0.5f*(postFix*sinf((t*d-s)*(2.0f*PI)/p)) + b; + } + + float postFix = a*powf(2.0f, -10.0f*(t-=1.0f)); + + return (postFix*sinf((t*d-s)*(2.0f*PI)/p)*0.5f + c + b); +} + +#ifdef __cplusplus +} +#endif + +#endif // EASINGS_H diff --git a/raylib/gestures.h b/raylib/gestures.h new file mode 100644 index 0000000..ece65b3 --- /dev/null +++ b/raylib/gestures.h @@ -0,0 +1,559 @@ +/********************************************************************************************** +* +* raylib.gestures - Gestures system, gestures processing based on input events (touch/mouse) +* +* NOTE: Memory footprint of this library is aproximately 128 bytes (global variables) +* +* CONFIGURATION: +* +* #define GESTURES_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define GESTURES_STANDALONE +* If defined, the library can be used as standalone to process gesture events with +* no external dependencies. +* +* CONTRIBUTORS: +* Marc Palau: Initial implementation (2014) +* Albert Martos: Complete redesign and testing (2015) +* Ian Eito: Complete redesign and testing (2015) +* Ramon Santamaria: Supervision, review, update and maintenance +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef GESTURES_H +#define GESTURES_H + +#ifndef PI + #define PI 3.14159265358979323846 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for GESTURES_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(GESTURES_STANDALONE) + #ifndef __cplusplus + // Boolean type + typedef enum { false, true } bool; + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Gestures type + // NOTE: It could be used as flags to enable only some gestures + typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP = 1, + GESTURE_DOUBLETAP = 2, + GESTURE_HOLD = 4, + GESTURE_DRAG = 8, + GESTURE_SWIPE_RIGHT = 16, + GESTURE_SWIPE_LEFT = 32, + GESTURE_SWIPE_UP = 64, + GESTURE_SWIPE_DOWN = 128, + GESTURE_PINCH_IN = 256, + GESTURE_PINCH_OUT = 512 + } Gestures; +#endif + +typedef enum { TOUCH_UP, TOUCH_DOWN, TOUCH_MOVE } TouchAction; + +// Gesture event +// NOTE: MAX_TOUCH_POINTS fixed to 4 +typedef struct { + int touchAction; + int pointCount; + int pointerId[4]; + Vector2 position[4]; +} GestureEvent; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +void ProcessGestureEvent(GestureEvent event); // Process gesture event and translate it into gestures +void UpdateGestures(void); // Update gestures detected (must be called every frame) + +#if defined(GESTURES_STANDALONE) +void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +bool IsGestureDetected(int gesture); // Check if a gesture have been detected +int GetGestureDetected(void); // Get latest detected gesture +int GetTouchPointsCount(void); // Get touch points count +float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds +Vector2 GetGestureDragVector(void); // Get gesture drag vector +float GetGestureDragAngle(void); // Get gesture drag angle +Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +float GetGesturePinchAngle(void); // Get gesture pinch angle +#endif + +#ifdef __cplusplus +} +#endif + +#endif // GESTURES_H + +/*********************************************************************************** +* +* GESTURES IMPLEMENTATION +* +************************************************************************************/ + +#if defined(GESTURES_IMPLEMENTATION) + +#if defined(_WIN32) + // Functions required to query time on Windows + int __stdcall QueryPerformanceCounter(unsigned long long int *lpPerformanceCount); + int __stdcall QueryPerformanceFrequency(unsigned long long int *lpFrequency); +#elif defined(__linux__) + #if _POSIX_C_SOURCE < 199309L + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. + #endif + #include // Required for: timespec + #include // Required for: clock_gettime() + + #include // Required for: sqrtf(), atan2f() +#endif +#if defined(__APPLE__) // macOS also defines __MACH__ + #include // Required for: clock_get_time() + #include // Required for: mach_timespec_t +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define FORCE_TO_SWIPE 0.0005f // Swipe force, measured in normalized screen units/time +#define MINIMUM_DRAG 0.015f // Drag minimum force, measured in normalized screen units (0.0f to 1.0f) +#define MINIMUM_PINCH 0.005f // Pinch minimum force, measured in normalized screen units (0.0f to 1.0f) +#define TAP_TIMEOUT 300 // Tap minimum time, measured in milliseconds +#define PINCH_TIMEOUT 300 // Pinch minimum time, measured in milliseconds +#define DOUBLETAP_RANGE 0.03f // DoubleTap range, measured in normalized screen units (0.0f to 1.0f) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Gestures module state context [136 bytes] +typedef struct { + unsigned int current; // Current detected gesture + unsigned int enabledFlags; // Enabled gestures flags + struct { + int firstId; // Touch id for first touch point + int pointCount; // Touch points counter + double eventTime; // Time stamp when an event happened + Vector2 upPosition; // Touch up position + Vector2 downPositionA; // First touch down position + Vector2 downPositionB; // Second touch down position + Vector2 downDragPosition; // Touch drag position + Vector2 moveDownPositionA; // First touch down position on move + Vector2 moveDownPositionB; // Second touch down position on move + int tapCounter; // TAP counter (one tap implies TOUCH_DOWN and TOUCH_UP actions) + } Touch; + struct { + bool resetRequired; // HOLD reset to get first touch point again + double timeDuration; // HOLD duration in milliseconds + } Hold; + struct { + Vector2 vector; // DRAG vector (between initial and current position) + float angle; // DRAG angle (relative to x-axis) + float distance; // DRAG distance (from initial touch point to final) (normalized [0..1]) + float intensity; // DRAG intensity, how far why did the DRAG (pixels per frame) + } Drag; + struct { + bool start; // SWIPE used to define when start measuring GESTURES.Swipe.timeDuration + double timeDuration; // SWIPE time to calculate drag intensity + } Swipe; + struct { + Vector2 vector; // PINCH vector (between first and second touch points) + float angle; // PINCH angle (relative to x-axis) + float distance; // PINCH displacement distance (normalized [0..1]) + } Pinch; +} GesturesData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GesturesData GESTURES = { + .Touch.firstId = -1, + .current = GESTURE_NONE, // No current gesture detected + .enabledFlags = 0b0000001111111111 // All gestures supported by default +}; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GESTURES_STANDALONE) +// Some required math functions provided by raymath.h +static float Vector2Angle(Vector2 initialPosition, Vector2 finalPosition); +static float Vector2Distance(Vector2 v1, Vector2 v2); +#endif +static double GetCurrentTime(void); + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Enable only desired getures to be detected +void SetGesturesEnabled(unsigned int flags) +{ + GESTURES.enabledFlags = flags; +} + +// Check if a gesture have been detected +bool IsGestureDetected(int gesture) +{ + if ((GESTURES.enabledFlags & GESTURES.current) == gesture) return true; + else return false; +} + +// Process gesture event and translate it into gestures +void ProcessGestureEvent(GestureEvent event) +{ + // Reset required variables + GESTURES.Touch.pointCount = event.pointCount; // Required on UpdateGestures() + + if (GESTURES.Touch.pointCount < 2) + { + if (event.touchAction == TOUCH_DOWN) + { + GESTURES.Touch.tapCounter++; // Tap counter + + // Detect GESTURE_DOUBLE_TAP + if ((GESTURES.current == GESTURE_NONE) && (GESTURES.Touch.tapCounter >= 2) && ((GetCurrentTime() - GESTURES.Touch.eventTime) < TAP_TIMEOUT) && (Vector2Distance(GESTURES.Touch.downPositionA, event.position[0]) < DOUBLETAP_RANGE)) + { + GESTURES.current = GESTURE_DOUBLETAP; + GESTURES.Touch.tapCounter = 0; + } + else // Detect GESTURE_TAP + { + GESTURES.Touch.tapCounter = 1; + GESTURES.current = GESTURE_TAP; + } + + GESTURES.Touch.downPositionA = event.position[0]; + GESTURES.Touch.downDragPosition = event.position[0]; + + GESTURES.Touch.upPosition = GESTURES.Touch.downPositionA; + GESTURES.Touch.eventTime = GetCurrentTime(); + + GESTURES.Touch.firstId = event.pointerId[0]; + + GESTURES.Drag.vector = (Vector2){ 0.0f, 0.0f }; + } + else if (event.touchAction == TOUCH_UP) + { + if (GESTURES.current == GESTURE_DRAG) GESTURES.Touch.upPosition = event.position[0]; + + // NOTE: GESTURES.Drag.intensity dependend on the resolution of the screen + GESTURES.Drag.distance = Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); + GESTURES.Drag.intensity = GESTURES.Drag.distance/(float)((GetCurrentTime() - GESTURES.Swipe.timeDuration)); + + GESTURES.Swipe.start = false; + + // Detect GESTURE_SWIPE + if ((GESTURES.Drag.intensity > FORCE_TO_SWIPE) && (GESTURES.Touch.firstId == event.pointerId[0])) + { + // NOTE: Angle should be inverted in Y + GESTURES.Drag.angle = 360.0f - Vector2Angle(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); + + if ((GESTURES.Drag.angle < 30) || (GESTURES.Drag.angle > 330)) GESTURES.current = GESTURE_SWIPE_RIGHT; // Right + else if ((GESTURES.Drag.angle > 30) && (GESTURES.Drag.angle < 120)) GESTURES.current = GESTURE_SWIPE_UP; // Up + else if ((GESTURES.Drag.angle > 120) && (GESTURES.Drag.angle < 210)) GESTURES.current = GESTURE_SWIPE_LEFT; // Left + else if ((GESTURES.Drag.angle > 210) && (GESTURES.Drag.angle < 300)) GESTURES.current = GESTURE_SWIPE_DOWN; // Down + else GESTURES.current = GESTURE_NONE; + } + else + { + GESTURES.Drag.distance = 0.0f; + GESTURES.Drag.intensity = 0.0f; + GESTURES.Drag.angle = 0.0f; + + GESTURES.current = GESTURE_NONE; + } + + GESTURES.Touch.downDragPosition = (Vector2){ 0.0f, 0.0f }; + GESTURES.Touch.pointCount = 0; + } + else if (event.touchAction == TOUCH_MOVE) + { + if (GESTURES.current == GESTURE_DRAG) GESTURES.Touch.eventTime = GetCurrentTime(); + + if (!GESTURES.Swipe.start) + { + GESTURES.Swipe.timeDuration = GetCurrentTime(); + GESTURES.Swipe.start = true; + } + + GESTURES.Touch.moveDownPositionA = event.position[0]; + + if (GESTURES.current == GESTURE_HOLD) + { + if (GESTURES.Hold.resetRequired) GESTURES.Touch.downPositionA = event.position[0]; + + GESTURES.Hold.resetRequired = false; + + // Detect GESTURE_DRAG + if (Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.moveDownPositionA) >= MINIMUM_DRAG) + { + GESTURES.Touch.eventTime = GetCurrentTime(); + GESTURES.current = GESTURE_DRAG; + } + } + + GESTURES.Drag.vector.x = GESTURES.Touch.moveDownPositionA.x - GESTURES.Touch.downDragPosition.x; + GESTURES.Drag.vector.y = GESTURES.Touch.moveDownPositionA.y - GESTURES.Touch.downDragPosition.y; + } + } + else // Two touch points + { + if (event.touchAction == TOUCH_DOWN) + { + GESTURES.Touch.downPositionA = event.position[0]; + GESTURES.Touch.downPositionB = event.position[1]; + + //GESTURES.Pinch.distance = Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.downPositionB); + + GESTURES.Pinch.vector.x = GESTURES.Touch.downPositionB.x - GESTURES.Touch.downPositionA.x; + GESTURES.Pinch.vector.y = GESTURES.Touch.downPositionB.y - GESTURES.Touch.downPositionA.y; + + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + } + else if (event.touchAction == TOUCH_MOVE) + { + GESTURES.Pinch.distance = Vector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); + + GESTURES.Touch.downPositionA = GESTURES.Touch.moveDownPositionA; + GESTURES.Touch.downPositionB = GESTURES.Touch.moveDownPositionB; + + GESTURES.Touch.moveDownPositionA = event.position[0]; + GESTURES.Touch.moveDownPositionB = event.position[1]; + + GESTURES.Pinch.vector.x = GESTURES.Touch.moveDownPositionB.x - GESTURES.Touch.moveDownPositionA.x; + GESTURES.Pinch.vector.y = GESTURES.Touch.moveDownPositionB.y - GESTURES.Touch.moveDownPositionA.y; + + if ((Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.moveDownPositionA) >= MINIMUM_PINCH) || (Vector2Distance(GESTURES.Touch.downPositionB, GESTURES.Touch.moveDownPositionB) >= MINIMUM_PINCH)) + { + if ((Vector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB) - GESTURES.Pinch.distance) < 0) GESTURES.current = GESTURE_PINCH_IN; + else GESTURES.current = GESTURE_PINCH_OUT; + } + else + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + } + + // NOTE: Angle should be inverted in Y + GESTURES.Pinch.angle = 360.0f - Vector2Angle(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); + } + else if (event.touchAction == TOUCH_UP) + { + GESTURES.Pinch.distance = 0.0f; + GESTURES.Pinch.angle = 0.0f; + GESTURES.Pinch.vector = (Vector2){ 0.0f, 0.0f }; + GESTURES.Touch.pointCount = 0; + + GESTURES.current = GESTURE_NONE; + } + } +} + +// Update gestures detected (must be called every frame) +void UpdateGestures(void) +{ + // NOTE: Gestures are processed through system callbacks on touch events + + // Detect GESTURE_HOLD + if (((GESTURES.current == GESTURE_TAP) || (GESTURES.current == GESTURE_DOUBLETAP)) && (GESTURES.Touch.pointCount < 2)) + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + } + + if (((GetCurrentTime() - GESTURES.Touch.eventTime) > TAP_TIMEOUT) && (GESTURES.current == GESTURE_DRAG) && (GESTURES.Touch.pointCount < 2)) + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + GESTURES.Hold.resetRequired = true; + } + + // Detect GESTURE_NONE + if ((GESTURES.current == GESTURE_SWIPE_RIGHT) || (GESTURES.current == GESTURE_SWIPE_UP) || (GESTURES.current == GESTURE_SWIPE_LEFT) || (GESTURES.current == GESTURE_SWIPE_DOWN)) + { + GESTURES.current = GESTURE_NONE; + } +} + +// Get number of touch points +int GetTouchPointsCount(void) +{ + // NOTE: point count is calculated when ProcessGestureEvent(GestureEvent event) is called + + return GESTURES.Touch.pointCount; +} + +// Get latest detected gesture +int GetGestureDetected(void) +{ + // Get current gesture only if enabled + return (GESTURES.enabledFlags & GESTURES.current); +} + +// Hold time measured in ms +float GetGestureHoldDuration(void) +{ + // NOTE: time is calculated on current gesture HOLD + + double time = 0.0; + + if (GESTURES.current == GESTURE_HOLD) time = GetCurrentTime() - GESTURES.Hold.timeDuration; + + return (float)time; +} + +// Get drag vector (between initial touch point to current) +Vector2 GetGestureDragVector(void) +{ + // NOTE: drag vector is calculated on one touch points TOUCH_MOVE + + return GESTURES.Drag.vector; +} + +// Get drag angle +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetGestureDragAngle(void) +{ + // NOTE: drag angle is calculated on one touch points TOUCH_UP + + return GESTURES.Drag.angle; +} + +// Get distance between two pinch points +Vector2 GetGesturePinchVector(void) +{ + // NOTE: The position values used for GESTURES.Pinch.distance are not modified like the position values of [core.c]-->GetTouchPosition(int index) + // NOTE: pinch distance is calculated on two touch points TOUCH_MOVE + + return GESTURES.Pinch.vector; +} + +// Get angle beween two pinch points +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetGesturePinchAngle(void) +{ + // NOTE: pinch angle is calculated on two touch points TOUCH_MOVE + + return GESTURES.Pinch.angle; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GESTURES_STANDALONE) +// Returns angle from two-points vector with X-axis +static float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float angle = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); + + if (angle < 0) angle += 360.0f; + + return angle; +} + +// Calculate distance between two Vector2 +static float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + + result = (float)sqrt(dx*dx + dy*dy); + + return result; +} +#endif + +// Time measure returned are milliseconds +static double GetCurrentTime(void) +{ + double time = 0; + +#if defined(_WIN32) + unsigned long long int clockFrequency, currentTime; + + QueryPerformanceFrequency(&clockFrequency); // BE CAREFUL: Costly operation! + QueryPerformanceCounter(¤tTime); + + time = (double)currentTime/clockFrequency*1000.0f; // Time in miliseconds +#endif + +#if defined(__linux__) + // NOTE: Only for Linux-based systems + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + unsigned long long int nowTime = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; // Time in nanoseconds + + time = ((double)nowTime/1000000.0); // Time in miliseconds +#endif + +#if defined(__APPLE__) + //#define CLOCK_REALTIME CALENDAR_CLOCK // returns UTC time since 1970-01-01 + //#define CLOCK_MONOTONIC SYSTEM_CLOCK // returns the time since boot time + + clock_serv_t cclock; + mach_timespec_t now; + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + + // NOTE: OS X does not have clock_gettime(), using clock_get_time() + clock_get_time(cclock, &now); + mach_port_deallocate(mach_task_self(), cclock); + unsigned long long int nowTime = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; // Time in nanoseconds + + time = ((double)nowTime/1000000.0); // Time in miliseconds +#endif + + return time; +} + +#endif // GESTURES_IMPLEMENTATION diff --git a/raylib/libraylib.a b/raylib/libraylib.a new file mode 100644 index 0000000..d8b3e9c Binary files /dev/null and b/raylib/libraylib.a differ diff --git a/raylib/models.c b/raylib/models.c new file mode 100644 index 0000000..feaadf8 --- /dev/null +++ b/raylib/models.c @@ -0,0 +1,4829 @@ +/********************************************************************************************** +* +* raylib.models - Basic functions to deal with 3d shapes and 3d models +* +* CONFIGURATION: +* +* #define SUPPORT_FILEFORMAT_OBJ +* #define SUPPORT_FILEFORMAT_MTL +* #define SUPPORT_FILEFORMAT_IQM +* #define SUPPORT_FILEFORMAT_GLTF +* Selected desired fileformats to be supported for model data loading. +* +* #define SUPPORT_MESH_GENERATION +* Support procedural mesh generation functions, uses external par_shapes.h library +* NOTE: Some generated meshes DO NOT include generated texture coordinates +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include "utils.h" // Required for: LoadFileData(), LoadFileText(), SaveFileText() + +#include // Required for: sprintf() +#include // Required for: malloc(), free() +#include // Required for: memcmp(), strlen() +#include // Required for: sinf(), cosf(), sqrtf(), fabsf() + +#if defined(_WIN32) + #include // Required for: _chdir() [Used in LoadOBJ()] + #define CHDIR _chdir +#else + #include // Required for: chdir() (POSIX) [Used in LoadOBJ()] + #define CHDIR chdir +#endif + +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 + +#if defined(SUPPORT_FILEFORMAT_OBJ) || defined(SUPPORT_FILEFORMAT_MTL) + #define TINYOBJ_MALLOC RL_MALLOC + #define TINYOBJ_CALLOC RL_CALLOC + #define TINYOBJ_REALLOC RL_REALLOC + #define TINYOBJ_FREE RL_FREE + + #define TINYOBJ_LOADER_C_IMPLEMENTATION + #include "external/tinyobj_loader_c.h" // OBJ/MTL file formats loading +#endif + +#if defined(SUPPORT_FILEFORMAT_GLTF) + #define CGLTF_MALLOC RL_MALLOC + #define CGLTF_FREE RL_FREE + + #define CGLTF_IMPLEMENTATION + #include "external/cgltf.h" // glTF file format loading + #include "external/stb_image.h" // glTF texture images loading +#endif + +#if defined(SUPPORT_MESH_GENERATION) + #define PAR_MALLOC(T, N) ((T*)RL_MALLOC(N*sizeof(T))) + #define PAR_CALLOC(T, N) ((T*)RL_CALLOC(N*sizeof(T), 1)) + #define PAR_REALLOC(T, BUF, N) ((T*)RL_REALLOC(BUF, sizeof(T)*(N))) + #define PAR_FREE RL_FREE + + #define PAR_SHAPES_IMPLEMENTATION + #include "external/par_shapes.h" // Shapes 3d parametric generation +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_OBJ) +static Model LoadOBJ(const char *fileName); // Load OBJ mesh data +#endif +#if defined(SUPPORT_FILEFORMAT_IQM) +static Model LoadIQM(const char *fileName); // Load IQM mesh data +static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCount); // Load IQM animation data +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) +static Model LoadGLTF(const char *fileName); // Load GLTF mesh data +static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data +static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex); +static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex); +static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex); +static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data); +static void InitGLTFBones(Model* model, const cgltf_data* data); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Draw a line in 3D world space +void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) +{ + // WARNING: Be careful with internal buffer vertex alignment + // when using RL_LINES or RL_TRIANGLES, data is aligned to fit + // lines-triangles-quads in the same indexed buffers!!! + rlCheckRenderBatchLimit(8); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(startPos.x, startPos.y, startPos.z); + rlVertex3f(endPos.x, endPos.y, endPos.z); + rlEnd(); +} + +// Draw a point in 3D space, actually a small line +void DrawPoint3D(Vector3 position, Color color) +{ + rlCheckRenderBatchLimit(8); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(0.0f, 0.0f, 0.0f); + rlVertex3f(0.0f, 0.0f, 0.1f); + rlEnd(); + rlPopMatrix(); +} + +// Draw a circle in 3D world space +void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color) +{ + rlCheckRenderBatchLimit(2*36); + + rlPushMatrix(); + rlTranslatef(center.x, center.y, center.z); + rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); + + rlBegin(RL_LINES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex3f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius, 0.0f); + rlVertex3f(sinf(DEG2RAD*(i + 10))*radius, cosf(DEG2RAD*(i + 10))*radius, 0.0f); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a color-filled triangle (vertex in counter-clockwise order!) +void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color) +{ + rlCheckRenderBatchLimit(3); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(v1.x, v1.y, v1.z); + rlVertex3f(v2.x, v2.y, v2.z); + rlVertex3f(v3.x, v3.y, v3.z); + rlEnd(); +} + +// Draw a triangle strip defined by points +void DrawTriangleStrip3D(Vector3 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + rlCheckRenderBatchLimit(3*(pointsCount - 2)); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 2; i < pointsCount; i++) + { + if ((i%2) == 0) + { + rlVertex3f(points[i].x, points[i].y, points[i].z); + rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z); + rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z); + } + else + { + rlVertex3f(points[i].x, points[i].y, points[i].z); + rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z); + rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z); + } + } + rlEnd(); + } +} + +// Draw cube +// NOTE: Cube position is the center position +void DrawCube(Vector3 position, float width, float height, float length, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlCheckRenderBatchLimit(36); + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + rlTranslatef(position.x, position.y, position.z); + //rlRotatef(45, 0, 1, 0); + //rlScalef(1.0f, 1.0f, 1.0f); // NOTE: Vertices are directly scaled on definition + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front face + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + + // Back face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + + // Top face + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + // Bottom face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + + rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + + // Right face + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + // Left face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right + + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlEnd(); + rlPopMatrix(); +} + +// Draw cube (Vector version) +void DrawCubeV(Vector3 position, Vector3 size, Color color) +{ + DrawCube(position, size.x, size.y, size.z, color); +} + +// Draw cube wires +void DrawCubeWires(Vector3 position, float width, float height, float length, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlCheckRenderBatchLimit(36); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front Face ----------------------------------------------------- + // Bottom Line + rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + + // Left Line + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Right + + // Top Line + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Right + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + + // Right Line + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + + // Back Face ------------------------------------------------------ + // Bottom Line + rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom Left + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + + // Left Line + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right + + // Top Line + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + + // Right Line + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom Left + + // Top Face ------------------------------------------------------- + // Left Line + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left Front + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left Back + + // Right Line + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Right Front + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right Back + + // Bottom Face --------------------------------------------------- + // Left Line + rlVertex3f(x-width/2, y-height/2, z+length/2); // Top Left Front + rlVertex3f(x-width/2, y-height/2, z-length/2); // Top Left Back + + // Right Line + rlVertex3f(x+width/2, y-height/2, z+length/2); // Top Right Front + rlVertex3f(x+width/2, y-height/2, z-length/2); // Top Right Back + rlEnd(); + rlPopMatrix(); +} + +// Draw cube wires (vector version) +void DrawCubeWiresV(Vector3 position, Vector3 size, Color color) +{ + DrawCubeWires(position, size.x, size.y, size.z, color); +} + +// Draw cube +// NOTE: Cube position is the center position +void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color) +{ + float x = position.x; + float y = position.y; + float z = position.z; + + rlCheckRenderBatchLimit(36); + + rlSetTexture(texture.id); + + //rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + //rlTranslatef(2.0f, 0.0f, 0.0f); + //rlRotatef(45, 0, 1, 0); + //rlScalef(2.0f, 2.0f, 2.0f); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + // Front Face + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left Of The Texture and Quad + // Back Face + rlNormal3f(0.0f, 0.0f, - 1.0f); // Normal Pointing Away From Viewer + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Left Of The Texture and Quad + // Top Face + rlNormal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad + // Bottom Face + rlNormal3f(0.0f, - 1.0f, 0.0f); // Normal Pointing Down + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad + // Right face + rlNormal3f(1.0f, 0.0f, 0.0f); // Normal Pointing Right + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left Of The Texture and Quad + // Left Face + rlNormal3f( - 1.0f, 0.0f, 0.0f); // Normal Pointing Left + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad + rlEnd(); + //rlPopMatrix(); + + rlSetTexture(0); +} + +// Draw sphere +void DrawSphere(Vector3 centerPos, float radius, Color color) +{ + DrawSphereEx(centerPos, radius, 16, 16, color); +} + +// Draw sphere with extended parameters +void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color) +{ + int numVertex = (rings + 2)*slices*6; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(radius, radius, radius); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < (rings + 2); i++) + { + for (int j = 0; j < slices; j++) + { + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + } + } + rlEnd(); + rlPopMatrix(); +} + +// Draw sphere wires +void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color) +{ + int numVertex = (rings + 2)*slices*6; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(radius, radius, radius); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < (rings + 2); i++) + { + for (int j = 0; j < slices; j++) + { + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + } + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a cylinder +// NOTE: It could be also used for pyramid and cone +void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color) +{ + if (sides < 3) sides = 3; + + int numVertex = sides*6; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + if (radiusTop > 0) + { + // Draw Body ------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); //Bottom Right + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); //Top Right + + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); //Top Left + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); //Top Right + } + + // Draw Cap -------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(0, height, 0); + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + } + } + else + { + // Draw Cone ------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(0, height, 0); + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + } + } + + // Draw Base ----------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(0, 0, 0); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a wired cylinder +// NOTE: It could be also used for pyramid and cone +void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color) +{ + if (sides < 3) sides = 3; + + int numVertex = sides*8; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); + + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a plane +void DrawPlane(Vector3 centerPos, Vector2 size, Color color) +{ + rlCheckRenderBatchLimit(4); + + // NOTE: Plane is always created on XZ ground + rlPushMatrix(); + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(size.x, 1.0f, size.y); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + rlNormal3f(0.0f, 1.0f, 0.0f); + + rlVertex3f(-0.5f, 0.0f, -0.5f); + rlVertex3f(-0.5f, 0.0f, 0.5f); + rlVertex3f(0.5f, 0.0f, 0.5f); + rlVertex3f(0.5f, 0.0f, -0.5f); + rlEnd(); + rlPopMatrix(); +} + +// Draw a ray line +void DrawRay(Ray ray, Color color) +{ + float scale = 10000; + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex3f(ray.position.x, ray.position.y, ray.position.z); + rlVertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale); + rlEnd(); +} + +// Draw a grid centered at (0, 0, 0) +void DrawGrid(int slices, float spacing) +{ + int halfSlices = slices/2; + + rlCheckRenderBatchLimit((slices + 2)*4); + + rlBegin(RL_LINES); + for (int i = -halfSlices; i <= halfSlices; i++) + { + if (i == 0) + { + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + } + else + { + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + } + + rlVertex3f((float)i*spacing, 0.0f, (float)-halfSlices*spacing); + rlVertex3f((float)i*spacing, 0.0f, (float)halfSlices*spacing); + + rlVertex3f((float)-halfSlices*spacing, 0.0f, (float)i*spacing); + rlVertex3f((float)halfSlices*spacing, 0.0f, (float)i*spacing); + } + rlEnd(); +} + +// Load model from files (mesh and material) +Model LoadModel(const char *fileName) +{ + Model model = { 0 }; + +#if defined(SUPPORT_FILEFORMAT_OBJ) + if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf;.glb")) model = LoadGLTF(fileName); +#endif + + // Make sure model transform is set to identity matrix! + model.transform = MatrixIdentity(); + + if (model.meshCount == 0) + { + model.meshCount = 1; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); +#if defined(SUPPORT_MESH_GENERATION) + TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load mesh data, default to cube mesh", fileName); + model.meshes[0] = GenMeshCube(1.0f, 1.0f, 1.0f); +#else + TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load mesh data", fileName); +#endif + } + else + { + // Upload vertex data to GPU (static mesh) + for (int i = 0; i < model.meshCount; i++) UploadMesh(&model.meshes[i], false); + } + + if (model.materialCount == 0) + { + TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to load material data, default to white material", fileName); + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + if (model.meshMaterial == NULL) model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + } + + return model; +} + +// Load model from generated mesh +// WARNING: A shallow copy of mesh is generated, passed by value, +// as long as struct contains pointers to data and some values, we get a copy +// of mesh pointing to same data as original version... be careful! +Model LoadModelFromMesh(Mesh mesh) +{ + Model model = { 0 }; + + model.transform = MatrixIdentity(); + + model.meshCount = 1; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshes[0] = mesh; + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + model.meshMaterial[0] = 0; // First material index + + return model; +} + +// Unload model (meshes/materials) from memory (RAM and/or VRAM) +// NOTE: This function takes care of all model elements, for a detailed control +// over them, use UnloadMesh() and UnloadMaterial() +void UnloadModel(Model model) +{ + // Unload meshes + for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]); + + // Unload materials maps + // NOTE: As the user could be sharing shaders and textures between models, + // we don't unload the material but just free it's maps, + // the user is responsible for freeing models shaders and textures + for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps); + + // Unload arrays + RL_FREE(model.meshes); + RL_FREE(model.materials); + RL_FREE(model.meshMaterial); + + // Unload animation data + RL_FREE(model.bones); + RL_FREE(model.bindPose); + + TRACELOG(LOG_INFO, "MODEL: Unloaded model (and meshes) from RAM and VRAM"); +} + +// Unload model (but not meshes) from memory (RAM and/or VRAM) +void UnloadModelKeepMeshes(Model model) +{ + // Unload materials maps + // NOTE: As the user could be sharing shaders and textures between models, + // we don't unload the material but just free it's maps, + // the user is responsible for freeing models shaders and textures + for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps); + + // Unload arrays + RL_FREE(model.meshes); + RL_FREE(model.materials); + RL_FREE(model.meshMaterial); + + // Unload animation data + RL_FREE(model.bones); + RL_FREE(model.bindPose); + + TRACELOG(LOG_INFO, "MODEL: Unloaded model (but not meshes) from RAM and VRAM"); +} + +// Upload vertex data into a VAO (if supported) and VBO +void UploadMesh(Mesh *mesh, bool dynamic) +{ + if (mesh->vaoId > 0) + { + // Check if mesh has already been loaded in GPU + TRACELOG(LOG_WARNING, "VAO: [ID %i] Trying to re-load an already loaded mesh", mesh->vaoId); + return; + } + + mesh->vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); + + mesh->vaoId = 0; // Vertex Array Object + mesh->vboId[0] = 0; // Vertex buffer: positions + mesh->vboId[1] = 0; // Vertex buffer: texcoords + mesh->vboId[2] = 0; // Vertex buffer: normals + mesh->vboId[3] = 0; // Vertex buffer: colors + mesh->vboId[4] = 0; // Vertex buffer: tangents + mesh->vboId[5] = 0; // Vertex buffer: texcoords2 + mesh->vboId[6] = 0; // Vertex buffer: indices + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mesh->vaoId = rlLoadVertexArray(); + rlEnableVertexArray(mesh->vaoId); + + // NOTE: Attributes must be uploaded considering default locations points + + // Enable vertex attributes: position (shader-location = 0) + mesh->vboId[0] = rlLoadVertexBuffer(mesh->vertices, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(0, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(0); + + // Enable vertex attributes: texcoords (shader-location = 1) + mesh->vboId[1] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(1, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(1); + + if (mesh->normals != NULL) + { + // Enable vertex attributes: normals (shader-location = 2) + mesh->vboId[2] = rlLoadVertexBuffer(mesh->normals, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(2, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(2); + } + else + { + // Default color vertex attribute set to WHITE + float value[3] = { 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(2, value, SHADER_ATTRIB_VEC3, 3); + rlDisableVertexAttribute(2); + } + + if (mesh->colors != NULL) + { + // Enable vertex attribute: color (shader-location = 3) + mesh->vboId[3] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic); + rlSetVertexAttribute(3, 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(3); + } + else + { + // Default color vertex attribute set to WHITE + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(3, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(3); + } + + if (mesh->tangents != NULL) + { + // Enable vertex attribute: tangent (shader-location = 4) + mesh->vboId[4] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic); + rlSetVertexAttribute(4, 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(4); + } + else + { + // Default tangents vertex attribute + float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + rlSetVertexAttributeDefault(4, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(4); + } + + if (mesh->texcoords2 != NULL) + { + // Enable vertex attribute: texcoord2 (shader-location = 5) + mesh->vboId[5] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(5, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(5); + } + else + { + // Default texcoord2 vertex attribute + float value[2] = { 0.0f, 0.0f }; + rlSetVertexAttributeDefault(5, value, SHADER_ATTRIB_VEC2, 2); + rlDisableVertexAttribute(5); + } + + if (mesh->indices != NULL) + { + mesh->vboId[6] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic); + } + + if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); + else TRACELOG(LOG_INFO, "VBO: Mesh uploaded successfully to VRAM (GPU)"); + + rlDisableVertexArray(); +#endif +} + +// Update mesh vertex data in GPU for a specific buffer index +void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset) +{ + rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset); +} + +// Draw a 3d mesh with material and transform +void DrawMesh(Mesh mesh, Material material, Matrix transform) +{ + DrawMeshInstanced(mesh, material, &transform, 1); +} + +// Draw multiple mesh instances with material and different transforms +void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_VERTEX_ARRAY 0x8074 + #define GL_NORMAL_ARRAY 0x8075 + #define GL_COLOR_ARRAY 0x8076 + #define GL_TEXTURE_COORD_ARRAY 0x8078 + + rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); + + rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices); + rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords); + rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals); + rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors); + + rlPushMatrix(); + rlMultMatrixf(MatrixToFloat(transforms[0])); + rlColor4ub(material.maps[MATERIAL_MAP_DIFFUSE].color.r, + material.maps[MATERIAL_MAP_DIFFUSE].color.g, + material.maps[MATERIAL_MAP_DIFFUSE].color.b, + material.maps[MATERIAL_MAP_DIFFUSE].color.a); + + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, mesh.indices); + else rlDrawVertexArray(0, mesh.vertexCount); + rlPopMatrix(); + + rlDisableStatePointer(GL_VERTEX_ARRAY); + rlDisableStatePointer(GL_TEXTURE_COORD_ARRAY); + rlDisableStatePointer(GL_NORMAL_ARRAY); + rlDisableStatePointer(GL_COLOR_ARRAY); + + rlDisableTexture(); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check instancing + bool instancing = false; + if (instances < 1) return; + else if (instances > 1) instancing = true; + float16 *instanceTransforms = NULL; + unsigned int instancesVboId = 0; + + // Bind shader program + rlEnableShader(material.shader.id); + + // Send required data to shader (matrices, values) + //----------------------------------------------------- + // Upload to shader material.colDiffuse + if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1) + { + float values[4] = { + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1); + } + + // Upload to shader material.colSpecular (if location available) + if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1) + { + float values[4] = { + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.r/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.g/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.b/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1); + } + + // Get a copy of current matrices to work with, + // just in case stereo render is required and we need to modify them + // NOTE: At this point the modelview matrix just contains the view matrix (camera) + // That's because BeginMode3D() sets it and there is no model-drawing function + // that modifies it, all use rlPushMatrix() and rlPopMatrix() + Matrix matView = rlGetMatrixModelview(); + Matrix matModelView = matView; + Matrix matProjection = rlGetMatrixProjection(); + + // Upload view and projection matrices (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView); + if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection); + + if (instancing) + { + // Create instances buffer + instanceTransforms = RL_MALLOC(instances*sizeof(float16)); + + // Fill buffer with instances transformations as float16 arrays + for (int i = 0; i < instances; i++) instanceTransforms[i] = MatrixToFloatV(transforms[i]); + + // Enable mesh VAO to attach new buffer + rlEnableVertexArray(mesh.vaoId); + + // This could alternatively use a static VBO and either glMapBuffer() or glBufferSubData(). + // It isn't clear which would be reliably faster in all cases and on all platforms, + // anecdotally glMapBuffer() seems very slow (syncs) while glBufferSubData() seems + // no faster, since we're transferring all the transform matrices anyway + instancesVboId = rlLoadVertexBuffer(instanceTransforms, instances*sizeof(float16), false); + + // Instances transformation matrices are send to shader attribute location: SHADER_LOC_MATRIX_MODEL + for (unsigned int i = 0; i < 4; i++) + { + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 4, RL_FLOAT, 0, sizeof(Matrix), (void *)(i*sizeof(Vector4))); + rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 1); + } + + rlDisableVertexBuffer(); + rlDisableVertexArray(); + + // Accumulate internal matrix transform (push/pop) and view matrix + // NOTE: In this case, model instance transformation must be computed in the shader + matModelView = MatrixMultiply(rlGetMatrixTransform(), matView); + } + else + { + // Model transformation matrix is send to shader uniform location: SHADER_LOC_MATRIX_MODEL + if (material.shader.locs[SHADER_LOC_MATRIX_MODEL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MODEL], transforms[0]); + + // Accumulate several transformations: + // matView: rlgl internal modelview matrix (actually, just view matrix) + // rlGetMatrixTransform(): rlgl internal transform matrix due to push/pop matrix stack + // transform: function parameter transformation + matModelView = MatrixMultiply(transforms[0], MatrixMultiply(rlGetMatrixTransform(), matView)); + } + + // Upload model normal matrix (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModelView))); + //----------------------------------------------------- + + // Bind active texture maps (if available) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Enable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id); + else rlEnableTexture(material.maps[i].texture.id); + + rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1); + } + } + + // Try binding vertex array objects (VAO) + // or use VBOs if not possible + if (!rlEnableVertexArray(mesh.vaoId)) + { + // Bind mesh VBO data: vertex position (shader-location = 0) + rlEnableVertexBuffer(mesh.vboId[0]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + rlEnableVertexBuffer(mesh.vboId[0]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind mesh VBO data: vertex texcoords (shader-location = 1) + rlEnableVertexBuffer(mesh.vboId[1]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1) + { + // Bind mesh VBO data: vertex normals (shader-location = 2) + rlEnableVertexBuffer(mesh.vboId[2]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]); + } + + // Bind mesh VBO data: vertex colors (shader-location = 3, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1) + { + if (mesh.vboId[3] != 0) + { + rlEnableVertexBuffer(mesh.vboId[3]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + else + { + // Set default value for unused attribute + // NOTE: Required when using default shader and no VAO support + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC2, 4); + rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + } + + // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1) + { + rlEnableVertexBuffer(mesh.vboId[4]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]); + } + + // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1) + { + rlEnableVertexBuffer(mesh.vboId[5]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); + } + + if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]); + } + + int eyesCount = 1; + if (rlIsStereoRenderEnabled()) eyesCount = 2; + + for (int eye = 0; eye < eyesCount; eye++) + { + // Calculate model-view-projection matrix (MVP) + Matrix matMVP = MatrixIdentity(); + if (eyesCount == 1) matMVP = MatrixMultiply(matModelView, matProjection); + else + { + // Setup current eye viewport (half screen width) + rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight()); + matMVP = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye)); + } + + // Send combined model-view-projection matrix to shader + rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matMVP); + + if (instancing) // Draw mesh instanced + { + if (mesh.indices != NULL) rlDrawVertexArrayElementsInstanced(0, mesh.triangleCount*3, 0, instances); + else rlDrawVertexArrayInstanced(0, mesh.vertexCount, instances); + } + else // Draw mesh + { + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, 0); + else rlDrawVertexArray(0, mesh.vertexCount); + } + } + + // Unbind all binded texture maps + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Disable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); + else rlDisableTexture(); + } + + // Disable all possible vertex array objects (or VBOs) + rlDisableVertexArray(); + rlDisableVertexBuffer(); + rlDisableVertexBufferElement(); + + // Disable shader program + rlDisableShader(); + + if (instancing) + { + // Remove instance transforms buffer + rlUnloadVertexBuffer(instancesVboId); + RL_FREE(instanceTransforms); + } + else + { + // Restore rlgl internal modelview and projection matrices + rlSetMatrixModelview(matView); + rlSetMatrixProjection(matProjection); + } +#endif +} + +// Unload mesh from memory (RAM and VRAM) +void UnloadMesh(Mesh mesh) +{ + // Unload rlgl mesh vboId data + rlUnloadVertexArray(mesh.vaoId); + + for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]); + RL_FREE(mesh.vboId); + + RL_FREE(mesh.vertices); + RL_FREE(mesh.texcoords); + RL_FREE(mesh.normals); + RL_FREE(mesh.colors); + RL_FREE(mesh.tangents); + RL_FREE(mesh.texcoords2); + RL_FREE(mesh.indices); + + RL_FREE(mesh.animVertices); + RL_FREE(mesh.animNormals); + RL_FREE(mesh.boneWeights); + RL_FREE(mesh.boneIds); +} + +// Export mesh data to file +bool ExportMesh(Mesh mesh, const char *fileName) +{ + bool success = false; + + if (IsFileExtension(fileName, ".obj")) + { + // Estimated data size, it should be enough... + int dataSize = mesh.vertexCount/3* (int)strlen("v 0000.00f 0000.00f 0000.00f") + + mesh.vertexCount/2* (int)strlen("vt 0.000f 0.00f") + + mesh.vertexCount/3* (int)strlen("vn 0.000f 0.00f 0.00f") + + mesh.triangleCount/3* (int)strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000"); + + // NOTE: Text data buffer size is estimated considering mesh data size + char *txtData = (char *)RL_CALLOC(dataSize + 2000, sizeof(char)); + + int bytesCount = 0; + bytesCount += sprintf(txtData + bytesCount, "# //////////////////////////////////////////////////////////////////////////////////\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // feedback and support: ray[at]raylib.com //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n"); + bytesCount += sprintf(txtData + bytesCount, "# Vertex Count: %i\n", mesh.vertexCount); + bytesCount += sprintf(txtData + bytesCount, "# Triangle Count: %i\n\n", mesh.triangleCount); + + bytesCount += sprintf(txtData + bytesCount, "g mesh\n"); + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) + { + bytesCount += sprintf(txtData + bytesCount, "v %.2f %.2f %.2f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]); + } + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2) + { + bytesCount += sprintf(txtData + bytesCount, "vt %.3f %.3f\n", mesh.texcoords[v], mesh.texcoords[v + 1]); + } + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) + { + bytesCount += sprintf(txtData + bytesCount, "vn %.3f %.3f %.3f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]); + } + + for (int i = 0; i < mesh.triangleCount; i += 3) + { + bytesCount += sprintf(txtData + bytesCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", i, i, i, i + 1, i + 1, i + 1, i + 2, i + 2, i + 2); + } + + bytesCount += sprintf(txtData + bytesCount, "\n"); + + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + } + else if (IsFileExtension(fileName, ".raw")) + { + // TODO: Support additional file formats to export mesh vertex data + } + + return success; +} + + +// Load materials from model file +Material *LoadMaterials(const char *fileName, int *materialCount) +{ + Material *materials = NULL; + unsigned int count = 0; + + // TODO: Support IQM and GLTF for materials parsing + +#if defined(SUPPORT_FILEFORMAT_MTL) + if (IsFileExtension(fileName, ".mtl")) + { + tinyobj_material_t *mats = NULL; + + int result = tinyobj_parse_mtl_file(&mats, &count, fileName); + if (result != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName); + + // TODO: Process materials to return + + tinyobj_materials_free(mats, count); + } +#else + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load material file", fileName); +#endif + + // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL) + if (materials != NULL) + { + for (unsigned int i = 0; i < count; i++) materials[i].shader = rlGetShaderDefault(); + } + + *materialCount = count; + return materials; +} + +// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +Material LoadMaterialDefault(void) +{ + Material material = { 0 }; + material.maps = (MaterialMap *)RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap)); + + material.shader = rlGetShaderDefault(); + material.maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // White texture (1x1 pixel) + //material.maps[MATERIAL_MAP_NORMAL].texture; // NOTE: By default, not set + //material.maps[MATERIAL_MAP_SPECULAR].texture; // NOTE: By default, not set + + material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; // Diffuse color + material.maps[MATERIAL_MAP_SPECULAR].color = WHITE; // Specular color + + return material; +} + +// Unload material from memory +void UnloadMaterial(Material material) +{ + // Unload material shader (avoid unloading default shader, managed by raylib) + if (material.shader.id != rlGetShaderDefault().id) UnloadShader(material.shader); + + // Unload loaded texture maps (avoid unloading default texture, managed by raylib) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id != rlGetTextureDefault().id) rlUnloadTexture(material.maps[i].texture.id); + } + + RL_FREE(material.maps); +} + +// Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +// NOTE: Previous texture should be manually unloaded +void SetMaterialTexture(Material *material, int mapType, Texture2D texture) +{ + material->maps[mapType].texture = texture; +} + +// Set the material for a mesh +void SetModelMeshMaterial(Model *model, int meshId, int materialId) +{ + if (meshId >= model->meshCount) TRACELOG(LOG_WARNING, "MESH: Id greater than mesh count"); + else if (materialId >= model->materialCount) TRACELOG(LOG_WARNING, "MATERIAL: Id greater than material count"); + else model->meshMaterial[meshId] = materialId; +} + +// Load model animations from file +ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount) +{ + ModelAnimation *animations = NULL; + +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) animations = LoadIQMModelAnimations(fileName, animCount); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadGLTFModelAnimations(fileName, animCount); +#endif + + return animations; +} + +// Update model animated vertex data (positions and normals) for a given frame +// NOTE: Updated data is uploaded to GPU +void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) +{ + if ((anim.frameCount > 0) && (anim.bones != NULL) && (anim.framePoses != NULL)) + { + if (frame >= anim.frameCount) frame = frame%anim.frameCount; + + for (int m = 0; m < model.meshCount; m++) + { + Vector3 animVertex = { 0 }; + Vector3 animNormal = { 0 }; + + Vector3 inTranslation = { 0 }; + Quaternion inRotation = { 0 }; + //Vector3 inScale = { 0 }; // Not used... + + Vector3 outTranslation = { 0 }; + Quaternion outRotation = { 0 }; + Vector3 outScale = { 0 }; + + int vCounter = 0; + int boneCounter = 0; + int boneId = 0; + float boneWeight = 0.0; + + for (int i = 0; i < model.meshes[m].vertexCount; i++) + { + model.meshes[m].animVertices[vCounter] = 0; + model.meshes[m].animVertices[vCounter + 1] = 0; + model.meshes[m].animVertices[vCounter + 2] = 0; + + model.meshes[m].animNormals[vCounter] = 0; + model.meshes[m].animNormals[vCounter + 1] = 0; + model.meshes[m].animNormals[vCounter + 2] = 0; + + for (int j = 0; j < 4; j++) + { + boneId = model.meshes[m].boneIds[boneCounter]; + boneWeight = model.meshes[m].boneWeights[boneCounter]; + inTranslation = model.bindPose[boneId].translation; + inRotation = model.bindPose[boneId].rotation; + //inScale = model.bindPose[boneId].scale; + outTranslation = anim.framePoses[frame][boneId].translation; + outRotation = anim.framePoses[frame][boneId].rotation; + outScale = anim.framePoses[frame][boneId].scale; + + // Vertices processing + // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position) + animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] }; + animVertex = Vector3Multiply(animVertex, outScale); + animVertex = Vector3Subtract(animVertex, inTranslation); + animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + animVertex = Vector3Add(animVertex, outTranslation); + model.meshes[m].animVertices[vCounter] += animVertex.x * boneWeight; + model.meshes[m].animVertices[vCounter + 1] += animVertex.y * boneWeight; + model.meshes[m].animVertices[vCounter + 2] += animVertex.z * boneWeight; + + // Normals processing + // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) + if (model.meshes[m].normals != NULL) + { + animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] }; + animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + model.meshes[m].animNormals[vCounter] += animNormal.x * boneWeight; + model.meshes[m].animNormals[vCounter + 1] += animNormal.y * boneWeight; + model.meshes[m].animNormals[vCounter + 2] += animNormal.z * boneWeight; + } + boneCounter += 1; + } + vCounter += 3; + } + + // Upload new vertex data to GPU for model drawing + rlUpdateVertexBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex position + rlUpdateVertexBuffer(model.meshes[m].vboId[2], model.meshes[m].animNormals, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex normals + } + } +} + +// Unload animation array data +void UnloadModelAnimations(ModelAnimation* animations, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) UnloadModelAnimation(animations[i]); + RL_FREE(animations); +} + +// Unload animation data +void UnloadModelAnimation(ModelAnimation anim) +{ + for (int i = 0; i < anim.frameCount; i++) RL_FREE(anim.framePoses[i]); + + RL_FREE(anim.bones); + RL_FREE(anim.framePoses); +} + +// Check model animation skeleton match +// NOTE: Only number of bones and parent connections are checked +bool IsModelAnimationValid(Model model, ModelAnimation anim) +{ + int result = true; + + if (model.boneCount != anim.boneCount) result = false; + else + { + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; } + } + } + + return result; +} + +#if defined(SUPPORT_MESH_GENERATION) +// Generate polygonal mesh +Mesh GenMeshPoly(int sides, float radius) +{ + Mesh mesh = { 0 }; + + if (sides < 3) return mesh; + + int vertexCount = sides*3; + + // Vertices definition + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + + float d = 0.0f, dStep = 360.0f/sides; + for (int v = 0; v < vertexCount; v += 3) + { + vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f }; + vertices[v + 1] = (Vector3){ sinf(DEG2RAD*d)*radius, 0.0f, cosf(DEG2RAD*d)*radius }; + vertices[v + 2] = (Vector3){sinf(DEG2RAD*(d+dStep))*radius, 0.0f, cosf(DEG2RAD*(d+dStep))*radius }; + d += dStep; + } + + // Normals definition + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); + for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f }; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = sides; + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + + // Upload vertex data to GPU (static mesh) + // NOTE: mesh.vboId array is allocated inside UploadMesh() + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate plane mesh (with subdivisions) +Mesh GenMeshPlane(float width, float length, int resX, int resZ) +{ + Mesh mesh = { 0 }; + +#define CUSTOM_MESH_GEN_PLANE +#if defined(CUSTOM_MESH_GEN_PLANE) + resX++; + resZ++; + + // Vertices definition + int vertexCount = resX*resZ; // vertices get reused for the faces + + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int z = 0; z < resZ; z++) + { + // [-length/2, length/2] + float zPos = ((float)z/(resZ - 1) - 0.5f)*length; + for (int x = 0; x < resX; x++) + { + // [-width/2, width/2] + float xPos = ((float)x/(resX - 1) - 0.5f)*width; + vertices[x + z*resX] = (Vector3){ xPos, 0.0f, zPos }; + } + } + + // Normals definition + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); + for (int v = 0; v < resZ; v++) + { + for (int u = 0; u < resX; u++) + { + texcoords[u + v*resX] = (Vector2){ (float)u/(resX - 1), (float)v/(resZ - 1) }; + } + } + + // Triangles definition (indices) + int numFaces = (resX - 1)*(resZ - 1); + int *triangles = (int *)RL_MALLOC(numFaces*6*sizeof(int)); + int t = 0; + for (int face = 0; face < numFaces; face++) + { + // Retrieve lower left corner from face ind + int i = face % (resX - 1) + (face/(resZ - 1)*resX); + + triangles[t++] = i + resX; + triangles[t++] = i + 1; + triangles[t++] = i; + + triangles[t++] = i + resX; + triangles[t++] = i + resX + 1; + triangles[t++] = i + 1; + } + + mesh.vertexCount = vertexCount; + mesh.triangleCount = numFaces*2; + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.indices = (unsigned short *)RL_MALLOC(mesh.triangleCount*3*sizeof(unsigned short)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + // Mesh indices array initialization + for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i]; + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + RL_FREE(triangles); + +#else // Use par_shapes library to generate plane mesh + + par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!! + par_shapes_scale(plane, width, length, 1.0f); + par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_translate(plane, -width/2, 0.0f, length/2); + + mesh.vertices = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(plane->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = plane->ntriangles*3; + mesh.triangleCount = plane->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = plane->points[plane->triangles[k]*3]; + mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2]; + + mesh.normals[k*3] = plane->normals[plane->triangles[k]*3]; + mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(plane); +#endif + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generated cuboid mesh +Mesh GenMeshCube(float width, float height, float length) +{ + Mesh mesh = { 0 }; + +#define CUSTOM_MESH_GEN_CUBE +#if defined(CUSTOM_MESH_GEN_CUBE) + float vertices[] = { + -width/2, -height/2, length/2, + width/2, -height/2, length/2, + width/2, height/2, length/2, + -width/2, height/2, length/2, + -width/2, -height/2, -length/2, + -width/2, height/2, -length/2, + width/2, height/2, -length/2, + width/2, -height/2, -length/2, + -width/2, height/2, -length/2, + -width/2, height/2, length/2, + width/2, height/2, length/2, + width/2, height/2, -length/2, + -width/2, -height/2, -length/2, + width/2, -height/2, -length/2, + width/2, -height/2, length/2, + -width/2, -height/2, length/2, + width/2, -height/2, -length/2, + width/2, height/2, -length/2, + width/2, height/2, length/2, + width/2, -height/2, length/2, + -width/2, -height/2, -length/2, + -width/2, -height/2, length/2, + -width/2, height/2, length/2, + -width/2, height/2, -length/2 + }; + + float texcoords[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + float normals[] = { + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f + }; + + mesh.vertices = (float *)RL_MALLOC(24*3*sizeof(float)); + memcpy(mesh.vertices, vertices, 24*3*sizeof(float)); + + mesh.texcoords = (float *)RL_MALLOC(24*2*sizeof(float)); + memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float)); + + mesh.normals = (float *)RL_MALLOC(24*3*sizeof(float)); + memcpy(mesh.normals, normals, 24*3*sizeof(float)); + + mesh.indices = (unsigned short *)RL_MALLOC(36*sizeof(unsigned short)); + + int k = 0; + + // Indices can be initialized right now + for (int i = 0; i < 36; i+=6) + { + mesh.indices[i] = 4*k; + mesh.indices[i+1] = 4*k+1; + mesh.indices[i+2] = 4*k+2; + mesh.indices[i+3] = 4*k; + mesh.indices[i+4] = 4*k+2; + mesh.indices[i+5] = 4*k+3; + + k++; + } + + mesh.vertexCount = 24; + mesh.triangleCount = 12; + +#else // Use par_shapes library to generate cube mesh +/* +// Platonic solids: +par_shapes_mesh* par_shapes_create_tetrahedron(); // 4 sides polyhedron (pyramid) +par_shapes_mesh* par_shapes_create_cube(); // 6 sides polyhedron (cube) +par_shapes_mesh* par_shapes_create_octahedron(); // 8 sides polyhedron (dyamond) +par_shapes_mesh* par_shapes_create_dodecahedron(); // 12 sides polyhedron +par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron +*/ + // Platonic solid generation: cube (6 sides) + // NOTE: No normals/texcoords generated by default + par_shapes_mesh *cube = par_shapes_create_cube(); + cube->tcoords = PAR_MALLOC(float, 2*cube->npoints); + for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; + par_shapes_scale(cube, width, height, length); + par_shapes_translate(cube, -width/2, 0.0f, -length/2); + par_shapes_compute_normals(cube); + + mesh.vertices = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cube->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cube->ntriangles*3; + mesh.triangleCount = cube->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cube->points[cube->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cube->normals[cube->triangles[k]*3]; + mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cube); +#endif + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate sphere mesh (standard sphere) +Mesh GenMeshSphere(float radius, int rings, int slices) +{ + Mesh mesh = { 0 }; + + if ((rings >= 3) && (slices >= 3)) + { + par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere"); + + return mesh; +} + +// Generate hemi-sphere mesh (half sphere, no bottom cap) +Mesh GenMeshHemiSphere(float radius, int rings, int slices) +{ + Mesh mesh = { 0 }; + + if ((rings >= 3) && (slices >= 3)) + { + if (radius < 0.0f) radius = 0.0f; + + par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere"); + + return mesh; +} + +// Generate cylinder mesh +Mesh GenMeshCylinder(float radius, float height, int slices) +{ + Mesh mesh = { 0 }; + + if (slices >= 3) + { + // Instance a cylinder that sits on the Z=0 plane using the given tessellation + // levels across the UV domain. Think of "slices" like a number of pizza + // slices, and "stacks" like a number of stacked rings. + // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale + par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); + par_shapes_scale(cylinder, radius, radius, height); + par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(cylinder, PI/2.0f, (float[]){ 0, 1, 0 }); + + // Generate an orientable disk shape (top cap) + par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 }); + capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints); + for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; + par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_translate(capTop, 0, height, 0); + + // Generate an orientable disk shape (bottom cap) + par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 }); + capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); + for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; + par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); + + par_shapes_merge_and_free(cylinder, capTop); + par_shapes_merge_and_free(cylinder, capBottom); + + mesh.vertices = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cylinder->ntriangles*3; + mesh.triangleCount = cylinder->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3]; + mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cylinder); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder"); + + return mesh; +} + +// Generate torus mesh +Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) +{ + Mesh mesh = { 0 }; + + if ((sides >= 3) && (radSeg >= 3)) + { + if (radius > 1.0f) radius = 1.0f; + else if (radius < 0.1f) radius = 0.1f; + + // Create a donut that sits on the Z=0 plane with the specified inner radius + // The outer radius can be controlled with par_shapes_scale + par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius); + par_shapes_scale(torus, size/2, size/2, size/2); + + mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = torus->ntriangles*3; + mesh.triangleCount = torus->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = torus->points[torus->triangles[k]*3]; + mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2]; + + mesh.normals[k*3] = torus->normals[torus->triangles[k]*3]; + mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(torus); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus"); + + return mesh; +} + +// Generate trefoil knot mesh +Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) +{ + Mesh mesh = { 0 }; + + if ((sides >= 3) && (radSeg >= 3)) + { + if (radius > 3.0f) radius = 3.0f; + else if (radius < 0.5f) radius = 0.5f; + + par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius); + par_shapes_scale(knot, size, size, size); + + mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = knot->ntriangles*3; + mesh.triangleCount = knot->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = knot->points[knot->triangles[k]*3]; + mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2]; + + mesh.normals[k*3] = knot->normals[knot->triangles[k]*3]; + mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(knot); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot"); + + return mesh; +} + +// Generate a mesh from heightmap +// NOTE: Vertex data is uploaded to GPU +Mesh GenMeshHeightmap(Image heightmap, Vector3 size) +{ + #define GRAY_VALUE(c) ((c.r+c.g+c.b)/3) + + Mesh mesh = { 0 }; + + int mapX = heightmap.width; + int mapZ = heightmap.height; + + Color *pixels = LoadImageColors(heightmap); + + // NOTE: One vertex per pixel + mesh.triangleCount = (mapX-1)*(mapZ-1)*2; // One quad every four pixels + + mesh.vertexCount = mesh.triangleCount*3; + + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.colors = NULL; + + int vCounter = 0; // Used to count vertices float by float + int tcCounter = 0; // Used to count texcoords float by float + int nCounter = 0; // Used to count normals float by float + + int trisCounter = 0; + + Vector3 scaleFactor = { size.x/mapX, size.y/255.0f, size.z/mapZ }; + + Vector3 vA; + Vector3 vB; + Vector3 vC; + Vector3 vN; + + for (int z = 0; z < mapZ-1; z++) + { + for (int x = 0; x < mapX-1; x++) + { + // Fill vertices array with data + //---------------------------------------------------------- + + // one triangle - 3 vertex + mesh.vertices[vCounter] = (float)x*scaleFactor.x; + mesh.vertices[vCounter + 1] = (float)GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 2] = (float)z*scaleFactor.z; + + mesh.vertices[vCounter + 3] = (float)x*scaleFactor.x; + mesh.vertices[vCounter + 4] = (float)GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 5] = (float)(z + 1)*scaleFactor.z; + + mesh.vertices[vCounter + 6] = (float)(x + 1)*scaleFactor.x; + mesh.vertices[vCounter + 7] = (float)GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 8] = (float)z*scaleFactor.z; + + // another triangle - 3 vertex + mesh.vertices[vCounter + 9] = mesh.vertices[vCounter + 6]; + mesh.vertices[vCounter + 10] = mesh.vertices[vCounter + 7]; + mesh.vertices[vCounter + 11] = mesh.vertices[vCounter + 8]; + + mesh.vertices[vCounter + 12] = mesh.vertices[vCounter + 3]; + mesh.vertices[vCounter + 13] = mesh.vertices[vCounter + 4]; + mesh.vertices[vCounter + 14] = mesh.vertices[vCounter + 5]; + + mesh.vertices[vCounter + 15] = (float)(x + 1)*scaleFactor.x; + mesh.vertices[vCounter + 16] = (float)GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 17] = (float)(z + 1)*scaleFactor.z; + vCounter += 18; // 6 vertex, 18 floats + + // Fill texcoords array with data + //-------------------------------------------------------------- + mesh.texcoords[tcCounter] = (float)x/(mapX - 1); + mesh.texcoords[tcCounter + 1] = (float)z/(mapZ - 1); + + mesh.texcoords[tcCounter + 2] = (float)x/(mapX - 1); + mesh.texcoords[tcCounter + 3] = (float)(z + 1)/(mapZ - 1); + + mesh.texcoords[tcCounter + 4] = (float)(x + 1)/(mapX - 1); + mesh.texcoords[tcCounter + 5] = (float)z/(mapZ - 1); + + mesh.texcoords[tcCounter + 6] = mesh.texcoords[tcCounter + 4]; + mesh.texcoords[tcCounter + 7] = mesh.texcoords[tcCounter + 5]; + + mesh.texcoords[tcCounter + 8] = mesh.texcoords[tcCounter + 2]; + mesh.texcoords[tcCounter + 9] = mesh.texcoords[tcCounter + 3]; + + mesh.texcoords[tcCounter + 10] = (float)(x + 1)/(mapX - 1); + mesh.texcoords[tcCounter + 11] = (float)(z + 1)/(mapZ - 1); + tcCounter += 12; // 6 texcoords, 12 floats + + // Fill normals array with data + //-------------------------------------------------------------- + for (int i = 0; i < 18; i += 9) + { + vA.x = mesh.vertices[nCounter + i]; + vA.y = mesh.vertices[nCounter + i + 1]; + vA.z = mesh.vertices[nCounter + i + 2]; + + vB.x = mesh.vertices[nCounter + i + 3]; + vB.y = mesh.vertices[nCounter + i + 4]; + vB.z = mesh.vertices[nCounter + i + 5]; + + vC.x = mesh.vertices[nCounter + i + 6]; + vC.y = mesh.vertices[nCounter + i + 7]; + vC.z = mesh.vertices[nCounter + i + 8]; + + vN = Vector3Normalize(Vector3CrossProduct(Vector3Subtract(vB, vA), Vector3Subtract(vC, vA))); + + mesh.normals[nCounter + i] = vN.x; + mesh.normals[nCounter + i + 1] = vN.y; + mesh.normals[nCounter + i + 2] = vN.z; + + mesh.normals[nCounter + i + 3] = vN.x; + mesh.normals[nCounter + i + 4] = vN.y; + mesh.normals[nCounter + i + 5] = vN.z; + + mesh.normals[nCounter + i + 6] = vN.x; + mesh.normals[nCounter + i + 7] = vN.y; + mesh.normals[nCounter + i + 8] = vN.z; + } + + nCounter += 18; // 6 vertex, 18 floats + trisCounter += 2; + } + } + + UnloadImageColors(pixels); // Unload pixels color data + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate a cubes mesh from pixel data +// NOTE: Vertex data is uploaded to GPU +Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) +{ + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + Mesh mesh = { 0 }; + + Color *pixels = LoadImageColors(cubicmap); + + int mapWidth = cubicmap.width; + int mapHeight = cubicmap.height; + + // NOTE: Max possible number of triangles numCubes*(12 triangles by cube) + int maxTriangles = cubicmap.width*cubicmap.height*12; + + int vCounter = 0; // Used to count vertices + int tcCounter = 0; // Used to count texcoords + int nCounter = 0; // Used to count normals + + float w = cubeSize.x; + float h = cubeSize.z; + float h2 = cubeSize.y; + + Vector3 *mapVertices = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); + Vector2 *mapTexcoords = (Vector2 *)RL_MALLOC(maxTriangles*3*sizeof(Vector2)); + Vector3 *mapNormals = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); + + // Define the 6 normals of the cube, we will combine them accordingly later... + Vector3 n1 = { 1.0f, 0.0f, 0.0f }; + Vector3 n2 = { -1.0f, 0.0f, 0.0f }; + Vector3 n3 = { 0.0f, 1.0f, 0.0f }; + Vector3 n4 = { 0.0f, -1.0f, 0.0f }; + Vector3 n5 = { 0.0f, 0.0f, -1.0f }; + Vector3 n6 = { 0.0f, 0.0f, 1.0f }; + + // NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6) + typedef struct RectangleF { + float x; + float y; + float width; + float height; + } RectangleF; + + RectangleF rightTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; + RectangleF leftTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; + RectangleF frontTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; + RectangleF backTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; + RectangleF topTexUV = { 0.0f, 0.5f, 0.5f, 0.5f }; + RectangleF bottomTexUV = { 0.5f, 0.5f, 0.5f, 0.5f }; + + for (int z = 0; z < mapHeight; ++z) + { + for (int x = 0; x < mapWidth; ++x) + { + // Define the 8 vertex of the cube, we will combine them accordingly later... + Vector3 v1 = { w*(x - 0.5f), h2, h*(z - 0.5f) }; + Vector3 v2 = { w*(x - 0.5f), h2, h*(z + 0.5f) }; + Vector3 v3 = { w*(x + 0.5f), h2, h*(z + 0.5f) }; + Vector3 v4 = { w*(x + 0.5f), h2, h*(z - 0.5f) }; + Vector3 v5 = { w*(x + 0.5f), 0, h*(z - 0.5f) }; + Vector3 v6 = { w*(x - 0.5f), 0, h*(z - 0.5f) }; + Vector3 v7 = { w*(x - 0.5f), 0, h*(z + 0.5f) }; + Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) }; + + // We check pixel color to be WHITE -> draw full cube + if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE)) + { + // Define triangles and checking collateral cubes + //------------------------------------------------ + + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + // WARNING: Not required for a WHITE cubes, created to allow seeing the map from outside + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v2; + mapVertices[vCounter + 2] = v3; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v3; + mapVertices[vCounter + 5] = v4; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v8; + mapVertices[vCounter + 2] = v7; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v5; + mapVertices[vCounter + 5] = v8; + vCounter += 6; + + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + tcCounter += 6; + + // Checking cube on bottom of current cube + if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1)) + { + // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v2; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v3; + mapVertices[vCounter + 3] = v3; + mapVertices[vCounter + 4] = v7; + mapVertices[vCounter + 5] = v8; + vCounter += 6; + + mapNormals[nCounter] = n6; + mapNormals[nCounter + 1] = n6; + mapNormals[nCounter + 2] = n6; + mapNormals[nCounter + 3] = n6; + mapNormals[nCounter + 4] = n6; + mapNormals[nCounter + 5] = n6; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ frontTexUV.x, frontTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height }; + tcCounter += 6; + } + + // Checking cube on top of current cube + if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0)) + { + // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v5; + mapVertices[vCounter + 2] = v6; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n5; + mapNormals[nCounter + 1] = n5; + mapNormals[nCounter + 2] = n5; + mapNormals[nCounter + 3] = n5; + mapNormals[nCounter + 4] = n5; + mapNormals[nCounter + 5] = n5; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ backTexUV.x, backTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + tcCounter += 6; + } + + // Checking cube on right of current cube + if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1)) + { + // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v3; + mapVertices[vCounter + 1] = v8; + mapVertices[vCounter + 2] = v4; + mapVertices[vCounter + 3] = v4; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n1; + mapNormals[nCounter + 1] = n1; + mapNormals[nCounter + 2] = n1; + mapNormals[nCounter + 3] = n1; + mapNormals[nCounter + 4] = n1; + mapNormals[nCounter + 5] = n1; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ rightTexUV.x, rightTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height }; + tcCounter += 6; + } + + // Checking cube on left of current cube + if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0)) + { + // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v6; + mapVertices[vCounter + 5] = v7; + vCounter += 6; + + mapNormals[nCounter] = n2; + mapNormals[nCounter + 1] = n2; + mapNormals[nCounter + 2] = n2; + mapNormals[nCounter + 3] = n2; + mapNormals[nCounter + 4] = n2; + mapNormals[nCounter + 5] = n2; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + tcCounter += 6; + } + } + // We check pixel color to be BLACK, we will only draw floor and roof + else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK)) + { + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v3; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v3; + vCounter += 6; + + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v8; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + tcCounter += 6; + } + } + } + + // Move data from mapVertices temp arays to vertices float array + mesh.vertexCount = vCounter; + mesh.triangleCount = vCounter/3; + + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.colors = NULL; + + int fCounter = 0; + + // Move vertices data + for (int i = 0; i < vCounter; i++) + { + mesh.vertices[fCounter] = mapVertices[i].x; + mesh.vertices[fCounter + 1] = mapVertices[i].y; + mesh.vertices[fCounter + 2] = mapVertices[i].z; + fCounter += 3; + } + + fCounter = 0; + + // Move normals data + for (int i = 0; i < nCounter; i++) + { + mesh.normals[fCounter] = mapNormals[i].x; + mesh.normals[fCounter + 1] = mapNormals[i].y; + mesh.normals[fCounter + 2] = mapNormals[i].z; + fCounter += 3; + } + + fCounter = 0; + + // Move texcoords data + for (int i = 0; i < tcCounter; i++) + { + mesh.texcoords[fCounter] = mapTexcoords[i].x; + mesh.texcoords[fCounter + 1] = mapTexcoords[i].y; + fCounter += 2; + } + + RL_FREE(mapVertices); + RL_FREE(mapNormals); + RL_FREE(mapTexcoords); + + UnloadImageColors(pixels); // Unload pixels color data + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} +#endif // SUPPORT_MESH_GENERATION + +// Compute mesh bounding box limits +// NOTE: minVertex and maxVertex should be transformed by model transform matrix +BoundingBox MeshBoundingBox(Mesh mesh) +{ + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = { 0 }; + Vector3 maxVertex = { 0 }; + + if (mesh.vertices != NULL) + { + minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + + for (int i = 1; i < mesh.vertexCount; i++) + { + minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + } + } + + // Create the bounding box + BoundingBox box = { 0 }; + box.min = minVertex; + box.max = maxVertex; + + return box; +} + +// Compute mesh tangents +// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates +// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html +void MeshTangents(Mesh *mesh) +{ + if (mesh->tangents == NULL) mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float)); + else TRACELOG(LOG_WARNING, "MESH: Tangents data already available, re-writting"); + + Vector3 *tan1 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3)); + Vector3 *tan2 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3)); + + for (int i = 0; i < mesh->vertexCount; i += 3) + { + // Get triangle vertices + Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] }; + Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] }; + Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] }; + + // Get triangle texcoords + Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] }; + Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] }; + Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] }; + + float x1 = v2.x - v1.x; + float y1 = v2.y - v1.y; + float z1 = v2.z - v1.z; + float x2 = v3.x - v1.x; + float y2 = v3.y - v1.y; + float z2 = v3.z - v1.z; + + float s1 = uv2.x - uv1.x; + float t1 = uv2.y - uv1.y; + float s2 = uv3.x - uv1.x; + float t2 = uv3.y - uv1.y; + + float div = s1*t2 - s2*t1; + float r = (div == 0.0f)? 0.0f : 1.0f/div; + + Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r }; + Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r }; + + tan1[i + 0] = sdir; + tan1[i + 1] = sdir; + tan1[i + 2] = sdir; + + tan2[i + 0] = tdir; + tan2[i + 1] = tdir; + tan2[i + 2] = tdir; + } + + // Compute tangents considering normals + for (int i = 0; i < mesh->vertexCount; ++i) + { + Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + Vector3 tangent = tan1[i]; + + // TODO: Review, not sure if tangent computation is right, just used reference proposed maths... + #if defined(COMPUTE_TANGENTS_METHOD_01) + Vector3 tmp = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent))); + tmp = Vector3Normalize(tmp); + mesh->tangents[i*4 + 0] = tmp.x; + mesh->tangents[i*4 + 1] = tmp.y; + mesh->tangents[i*4 + 2] = tmp.z; + mesh->tangents[i*4 + 3] = 1.0f; + #else + Vector3OrthoNormalize(&normal, &tangent); + mesh->tangents[i*4 + 0] = tangent.x; + mesh->tangents[i*4 + 1] = tangent.y; + mesh->tangents[i*4 + 2] = tangent.z; + mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f; + #endif + } + + RL_FREE(tan1); + RL_FREE(tan2); + + // Load a new tangent attributes buffer + mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false); + + TRACELOG(LOG_INFO, "MESH: Tangents data computed for provided mesh"); +} + +// Compute mesh binormals (aka bitangent) +void MeshBinormals(Mesh *mesh) +{ + for (int i = 0; i < mesh->vertexCount; i++) + { + //Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + //Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; + //Vector3 binormal = Vector3Scale(Vector3CrossProduct(normal, tangent), mesh->tangents[i*4 + 3]); + + // TODO: Register computed binormal in mesh->binormal? + } +} + +// Draw a model (with texture if set) +void DrawModel(Model model, Vector3 position, float scale, Color tint) +{ + Vector3 vScale = { scale, scale, scale }; + Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f }; + + DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint); +} + +// Draw a model with extended parameters +void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + // Calculate transformation matrix from function parameters + // Get transform matrix (rotation -> scale -> translation) + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); + Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); + + Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); + + // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform) + model.transform = MatrixMultiply(model.transform, matTransform); + + for (int i = 0; i < model.meshCount; i++) + { + Color color = model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color; + + Color colorTint = WHITE; + colorTint.r = (unsigned char)((((float)color.r/255.0)*((float)tint.r/255.0))*255.0f); + colorTint.g = (unsigned char)((((float)color.g/255.0)*((float)tint.g/255.0))*255.0f); + colorTint.b = (unsigned char)((((float)color.b/255.0)*((float)tint.b/255.0))*255.0f); + colorTint.a = (unsigned char)((((float)color.a/255.0)*((float)tint.a/255.0))*255.0f); + + model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = colorTint; + DrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform); + model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = color; + } +} + +// Draw a model wires (with texture if set) +void DrawModelWires(Model model, Vector3 position, float scale, Color tint) +{ + rlEnableWireMode(); + + DrawModel(model, position, scale, tint); + + rlDisableWireMode(); +} + +// Draw a model wires (with texture if set) with extended parameters +void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + rlEnableWireMode(); + + DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint); + + rlDisableWireMode(); +} + +// Draw a billboard +void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint) +{ + Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + + DrawBillboardRec(camera, texture, source, center, size, tint); +} + +// Draw a billboard (part of a texture defined by a rectangle) +void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 center, float size, Color tint) +{ + // NOTE: Billboard size will maintain source rectangle aspect ratio, size will represent billboard width + Vector2 sizeRatio = { size, size*(float)source.height/source.width }; + + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + Vector3 right = { matView.m0, matView.m4, matView.m8 }; + //Vector3 up = { matView.m1, matView.m5, matView.m9 }; + + // NOTE: Billboard locked on axis-Y + Vector3 up = { 0.0f, 1.0f, 0.0f }; +/* + a-------b + | | + | * | + | | + d-------c +*/ + right = Vector3Scale(right, sizeRatio.x/2); + up = Vector3Scale(up, sizeRatio.y/2); + + Vector3 p1 = Vector3Add(right, up); + Vector3 p2 = Vector3Subtract(right, up); + + Vector3 a = Vector3Subtract(center, p2); + Vector3 b = Vector3Add(center, p1); + Vector3 c = Vector3Add(center, p2); + Vector3 d = Vector3Subtract(center, p1); + + rlCheckRenderBatchLimit(4); + + rlSetTexture(texture.id); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + + // Bottom-left corner for texture and quad + rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height); + rlVertex3f(a.x, a.y, a.z); + + // Top-left corner for texture and quad + rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height); + rlVertex3f(d.x, d.y, d.z); + + // Top-right corner for texture and quad + rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height); + rlVertex3f(c.x, c.y, c.z); + + // Bottom-right corner for texture and quad + rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height); + rlVertex3f(b.x, b.y, b.z); + rlEnd(); + + rlSetTexture(0); +} + +// Draw a bounding box with wires +void DrawBoundingBox(BoundingBox box, Color color) +{ + Vector3 size; + + size.x = fabsf(box.max.x - box.min.x); + size.y = fabsf(box.max.y - box.min.y); + size.z = fabsf(box.max.z - box.min.z); + + Vector3 center = { box.min.x + size.x/2.0f, box.min.y + size.y/2.0f, box.min.z + size.z/2.0f }; + + DrawCubeWires(center, size.x, size.y, size.z, color); +} + +// Detect collision between two spheres +bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2) +{ + bool collision = false; + + // Simple way to check for collision, just checking distance between two points + // Unfortunately, sqrtf() is a costly operation, so we avoid it with following solution + /* + float dx = center1.x - center2.x; // X distance between centers + float dy = center1.y - center2.y; // Y distance between centers + float dz = center1.z - center2.z; // Z distance between centers + + float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers + + if (distance <= (radius1 + radius2)) collision = true; + */ + + // Check for distances squared to avoid sqrtf() + if (Vector3DotProduct(Vector3Subtract(center2, center1), Vector3Subtract(center2, center1)) <= (radius1 + radius2)*(radius1 + radius2)) collision = true; + + return collision; +} + +// Detect collision between two boxes +// NOTE: Boxes are defined by two points minimum and maximum +bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2) +{ + bool collision = true; + + if ((box1.max.x >= box2.min.x) && (box1.min.x <= box2.max.x)) + { + if ((box1.max.y < box2.min.y) || (box1.min.y > box2.max.y)) collision = false; + if ((box1.max.z < box2.min.z) || (box1.min.z > box2.max.z)) collision = false; + } + else collision = false; + + return collision; +} + +// Detect collision between box and sphere +bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius) +{ + bool collision = false; + + float dmin = 0; + + if (center.x < box.min.x) dmin += powf(center.x - box.min.x, 2); + else if (center.x > box.max.x) dmin += powf(center.x - box.max.x, 2); + + if (center.y < box.min.y) dmin += powf(center.y - box.min.y, 2); + else if (center.y > box.max.y) dmin += powf(center.y - box.max.y, 2); + + if (center.z < box.min.z) dmin += powf(center.z - box.min.z, 2); + else if (center.z > box.max.z) dmin += powf(center.z - box.max.z, 2); + + if (dmin <= (radius*radius)) collision = true; + + return collision; +} + +// Detect collision between ray and sphere +bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius) +{ + bool collision = false; + + Vector3 raySpherePos = Vector3Subtract(center, ray.position); + float distance = Vector3Length(raySpherePos); + float vector = Vector3DotProduct(raySpherePos, ray.direction); + float d = radius*radius - (distance*distance - vector*vector); + + if (d >= 0.0f) collision = true; + + return collision; +} + +// Detect collision between ray and sphere with extended parameters and collision point detection +bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint) +{ + bool collision = false; + + Vector3 raySpherePos = Vector3Subtract(center, ray.position); + float distance = Vector3Length(raySpherePos); + float vector = Vector3DotProduct(raySpherePos, ray.direction); + float d = radius*radius - (distance*distance - vector*vector); + + if (d >= 0.0f) collision = true; + + // Check if ray origin is inside the sphere to calculate the correct collision point + float collisionDistance = 0; + + if (distance < radius) collisionDistance = vector + sqrtf(d); + else collisionDistance = vector - sqrtf(d); + + // Calculate collision point + Vector3 cPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, collisionDistance)); + + collisionPoint->x = cPoint.x; + collisionPoint->y = cPoint.y; + collisionPoint->z = cPoint.z; + + return collision; +} + +// Detect collision between ray and bounding box +bool CheckCollisionRayBox(Ray ray, BoundingBox box) +{ + bool collision = false; + + float t[8]; + t[0] = (box.min.x - ray.position.x)/ray.direction.x; + t[1] = (box.max.x - ray.position.x)/ray.direction.x; + t[2] = (box.min.y - ray.position.y)/ray.direction.y; + t[3] = (box.max.y - ray.position.y)/ray.direction.y; + t[4] = (box.min.z - ray.position.z)/ray.direction.z; + t[5] = (box.max.z - ray.position.z)/ray.direction.z; + t[6] = (float)fmax(fmax(fmin(t[0], t[1]), fmin(t[2], t[3])), fmin(t[4], t[5])); + t[7] = (float)fmin(fmin(fmax(t[0], t[1]), fmax(t[2], t[3])), fmax(t[4], t[5])); + + collision = !(t[7] < 0 || t[6] > t[7]); + + return collision; +} +// Get collision info between ray and mesh +RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform) +{ + RayHitInfo result = { 0 }; + + // Check if mesh vertex data on CPU for testing + if (mesh.vertices != NULL) + { + int triangleCount = mesh.triangleCount; + + // Test against all triangles in mesh + for (int i = 0; i < triangleCount; i++) + { + Vector3 a, b, c; + Vector3* vertdata = (Vector3*)mesh.vertices; + + if (mesh.indices) + { + a = vertdata[mesh.indices[i*3 + 0]]; + b = vertdata[mesh.indices[i*3 + 1]]; + c = vertdata[mesh.indices[i*3 + 2]]; + } + else + { + a = vertdata[i*3 + 0]; + b = vertdata[i*3 + 1]; + c = vertdata[i*3 + 2]; + } + + a = Vector3Transform(a, transform); + b = Vector3Transform(b, transform); + c = Vector3Transform(c, transform); + + RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); + + if (triHitInfo.hit) + { + // Save the closest hit triangle + if ((!result.hit) || (result.distance > triHitInfo.distance)) result = triHitInfo; + } + } + } + return result; +} + +// Get collision info between ray and model +RayHitInfo GetCollisionRayModel(Ray ray, Model model) +{ + RayHitInfo result = { 0 }; + + for (int m = 0; m < model.meshCount; m++) + { + RayHitInfo meshHitInfo = GetCollisionRayMesh(ray, model.meshes[m], model.transform); + + if (meshHitInfo.hit) + { + // Save the closest hit mesh + if ((!result.hit) || (result.distance > meshHitInfo.distance)) result = meshHitInfo; + } + } + + return result; +} + +// Get collision info between ray and triangle +// NOTE: Based on https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm +RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) +{ + #define EPSILON 0.000001 // A small number + + Vector3 edge1, edge2; + Vector3 p, q, tv; + float det, invDet, u, v, t; + RayHitInfo result = {0}; + + // Find vectors for two edges sharing V1 + edge1 = Vector3Subtract(p2, p1); + edge2 = Vector3Subtract(p3, p1); + + // Begin calculating determinant - also used to calculate u parameter + p = Vector3CrossProduct(ray.direction, edge2); + + // If determinant is near zero, ray lies in plane of triangle or ray is parallel to plane of triangle + det = Vector3DotProduct(edge1, p); + + // Avoid culling! + if ((det > -EPSILON) && (det < EPSILON)) return result; + + invDet = 1.0f/det; + + // Calculate distance from V1 to ray origin + tv = Vector3Subtract(ray.position, p1); + + // Calculate u parameter and test bound + u = Vector3DotProduct(tv, p)*invDet; + + // The intersection lies outside of the triangle + if ((u < 0.0f) || (u > 1.0f)) return result; + + // Prepare to test v parameter + q = Vector3CrossProduct(tv, edge1); + + // Calculate V parameter and test bound + v = Vector3DotProduct(ray.direction, q)*invDet; + + // The intersection lies outside of the triangle + if ((v < 0.0f) || ((u + v) > 1.0f)) return result; + + t = Vector3DotProduct(edge2, q)*invDet; + + if (t > EPSILON) + { + // Ray hit, get hit point and normal + result.hit = true; + result.distance = t; + result.hit = true; + result.normal = Vector3Normalize(Vector3CrossProduct(edge1, edge2)); + result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, t)); + } + + return result; +} + +// Get collision info between ray and ground plane (Y-normal plane) +RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight) +{ + #define EPSILON 0.000001 // A small number + + RayHitInfo result = { 0 }; + + if (fabsf(ray.direction.y) > EPSILON) + { + float distance = (ray.position.y - groundHeight)/-ray.direction.y; + + if (distance >= 0.0) + { + result.hit = true; + result.distance = distance; + result.normal = (Vector3){ 0.0, 1.0, 0.0 }; + result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, distance)); + result.position.y = groundHeight; + } + } + + return result; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +#if defined(SUPPORT_FILEFORMAT_OBJ) +// Load OBJ mesh data +static Model LoadOBJ(const char *fileName) +{ + Model model = { 0 }; + + tinyobj_attrib_t attrib = { 0 }; + tinyobj_shape_t *meshes = NULL; + unsigned int meshCount = 0; + + tinyobj_material_t *materials = NULL; + unsigned int materialCount = 0; + + char *fileData = LoadFileText(fileName); + + if (fileData != NULL) + { + unsigned int dataSize = (unsigned int)strlen(fileData); + char currentDir[1024] = { 0 }; + strcpy(currentDir, GetWorkingDirectory()); + const char *workingDir = GetDirectoryPath(fileName); + if (CHDIR(workingDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + } + + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileData, dataSize, flags); + + if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName); + else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes / %i materials", fileName, meshCount, materialCount); + + model.meshCount = materialCount; + + // Init model materials array + if (materialCount > 0) + { + model.materialCount = materialCount; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + TraceLog(LOG_INFO, "MODEL: model has %i material meshes", materialCount); + } + else + { + model.meshCount = 1; + TraceLog(LOG_INFO, "MODEL: No materials, putting all meshes in a default material"); + } + + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + + // count the faces for each material + int *matFaces = RL_CALLOC(meshCount, sizeof(int)); + + for (unsigned int mi = 0; mi < meshCount; mi++) + { + for (unsigned int fi = 0; fi < meshes[mi].length; fi++) + { + int idx = attrib.material_ids[meshes[mi].face_offset + fi]; + if (idx == -1) idx = 0; // for no material face (which could be the whole model) + matFaces[idx]++; + } + } + + //-------------------------------------- + // create the material meshes + + // running counts / indexes for each material mesh as we are + // building them at the same time + int *vCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *vtCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *vnCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *faceCount = RL_CALLOC(model.meshCount, sizeof(int)); + + // allocate space for each of the material meshes + for (int mi = 0; mi < model.meshCount; mi++) + { + model.meshes[mi].vertexCount = matFaces[mi]*3; + model.meshes[mi].triangleCount = matFaces[mi]; + model.meshes[mi].vertices = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float)); + model.meshes[mi].texcoords = (float *)RL_CALLOC(model.meshes[mi].vertexCount*2, sizeof(float)); + model.meshes[mi].normals = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float)); + model.meshMaterial[mi] = mi; + } + + // scan through the combined sub meshes and pick out each material mesh + for (unsigned int af = 0; af < attrib.num_faces; af++) + { + int mm = attrib.material_ids[af]; // mesh material for this face + if (mm == -1) { mm = 0; } // no material object.. + + // Get indices for the face + tinyobj_vertex_index_t idx0 = attrib.faces[3*af + 0]; + tinyobj_vertex_index_t idx1 = attrib.faces[3*af + 1]; + tinyobj_vertex_index_t idx2 = attrib.faces[3*af + 2]; + + // Fill vertices buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount[mm] +=3; + + if (attrib.num_texcoords > 0) + { + // Fill texcoords buffer (float) using vertex index of the face + // NOTE: Y-coordinate must be flipped upside-down to account for + // raylib's upside down textures... + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount[mm] += 2; + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount[mm] += 2; + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount[mm] += 2; + } + + if (attrib.num_normals > 0) + { + // Fill normals buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount[mm] +=3; + } + } + + // Init model materials + for (unsigned int m = 0; m < materialCount; m++) + { + // Init material to default + // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE + model.materials[m] = LoadMaterialDefault(); + + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // Get default texture, in case no texture is defined + + if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname); //char *diffuse_texname; // map_Kd + else model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); + + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].color = (Color){ (unsigned char)(materials[m].diffuse[0]*255.0f), (unsigned char)(materials[m].diffuse[1]*255.0f), (unsigned char)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f; + + if (materials[m].specular_texname != NULL) model.materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(materials[m].specular_texname); //char *specular_texname; // map_Ks + model.materials[m].maps[MATERIAL_MAP_SPECULAR].color = (Color){ (unsigned char)(materials[m].specular[0]*255.0f), (unsigned char)(materials[m].specular[1]*255.0f), (unsigned char)(materials[m].specular[2]*255.0f), 255 }; //float specular[3]; + model.materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f; + + if (materials[m].bump_texname != NULL) model.materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(materials[m].bump_texname); //char *bump_texname; // map_bump, bump + model.materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE; + model.materials[m].maps[MATERIAL_MAP_NORMAL].value = materials[m].shininess; + + model.materials[m].maps[MATERIAL_MAP_EMISSION].color = (Color){ (unsigned char)(materials[m].emission[0]*255.0f), (unsigned char)(materials[m].emission[1]*255.0f), (unsigned char)(materials[m].emission[2]*255.0f), 255 }; //float emission[3]; + + if (materials[m].displacement_texname != NULL) model.materials[m].maps[MATERIAL_MAP_HEIGHT].texture = LoadTexture(materials[m].displacement_texname); //char *displacement_texname; // disp + } + + tinyobj_attrib_free(&attrib); + tinyobj_shapes_free(meshes, meshCount); + tinyobj_materials_free(materials, materialCount); + + RL_FREE(fileData); + RL_FREE(matFaces); + + RL_FREE(vCount); + RL_FREE(vtCount); + RL_FREE(vnCount); + RL_FREE(faceCount); + + if (CHDIR(currentDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); + } + } + + return model; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_IQM) +// Load IQM mesh data +static Model LoadIQM(const char *fileName) +{ + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // only IQM version 2 supported + + #define BONE_NAME_LENGTH 32 // BoneInfo name string length + #define MESH_NAME_LENGTH 32 // Mesh name string length + #define MATERIAL_NAME_LENGTH 32 // Material name string length + + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + unsigned char *fileDataPtr = fileData; + + // IQM file structs + //----------------------------------------------------------------------------------- + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMMesh { + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; + } IQMMesh; + + typedef struct IQMTriangle { + unsigned int vertex[3]; + } IQMTriangle; + + typedef struct IQMJoint { + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; + } IQMJoint; + + typedef struct IQMVertexArray { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; + } IQMVertexArray; + + // NOTE: Below IQM structures are not used but listed for reference + /* + typedef struct IQMAdjacency { + unsigned int triangle[3]; + } IQMAdjacency; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + typedef struct IQMBounds { + float bbmin[3], bbmax[3]; + float xyradius, radius; + } IQMBounds; + */ + //----------------------------------------------------------------------------------- + + // IQM vertex data types + enum { + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, // NOTE: Tangents unused by default + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, // NOTE: Vertex colors unused by default + IQM_CUSTOM = 0x10 // NOTE: Custom vertex values unused by default + }; + + Model model = { 0 }; + + IQMMesh *imesh = NULL; + IQMTriangle *tri = NULL; + IQMVertexArray *va = NULL; + IQMJoint *ijoint = NULL; + + float *vertex = NULL; + float *normal = NULL; + float *text = NULL; + char *blendi = NULL; + unsigned char *blendw = NULL; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return model; + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + return model; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + return model; + } + + //fileDataPtr += sizeof(IQMHeader); // Move file data pointer + + // Meshes data processing + imesh = RL_MALLOC(sizeof(IQMMesh)*iqmHeader->num_meshes); + //fseek(iqmFile, iqmHeader->ofs_meshes, SEEK_SET); + //fread(imesh, sizeof(IQMMesh)*iqmHeader->num_meshes, 1, iqmFile); + memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh)); + + model.meshCount = iqmHeader->num_meshes; + model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + + model.materialCount = model.meshCount; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + + char name[MESH_NAME_LENGTH] = { 0 }; + char material[MATERIAL_NAME_LENGTH] = { 0 }; + + for (int i = 0; i < model.meshCount; i++) + { + //fseek(iqmFile, iqmHeader->ofs_text + imesh[i].name, SEEK_SET); + //fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile); + memcpy(name, fileDataPtr + iqmHeader->ofs_text + imesh[i].name, MESH_NAME_LENGTH*sizeof(char)); + + //fseek(iqmFile, iqmHeader->ofs_text + imesh[i].material, SEEK_SET); + //fread(material, sizeof(char)*MATERIAL_NAME_LENGTH, 1, iqmFile); + memcpy(material, fileDataPtr + iqmHeader->ofs_text + imesh[i].material, MATERIAL_NAME_LENGTH*sizeof(char)); + + model.materials[i] = LoadMaterialDefault(); + + TRACELOG(LOG_DEBUG, "MODEL: [%s] mesh name (%s), material (%s)", fileName, name, material); + + model.meshes[i].vertexCount = imesh[i].num_vertexes; + + model.meshes[i].vertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions + model.meshes[i].normals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex normals + model.meshes[i].texcoords = RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); // Default vertex texcoords + + model.meshes[i].boneIds = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + model.meshes[i].boneWeights = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + + model.meshes[i].triangleCount = imesh[i].num_triangles; + model.meshes[i].indices = RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short)); + + // Animated verted data, what we actually process for rendering + // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) + model.meshes[i].animVertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); + model.meshes[i].animNormals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); + } + + // Triangles data processing + tri = RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle)); + //fseek(iqmFile, iqmHeader->ofs_triangles, SEEK_SET); + //fread(tri, iqmHeader->num_triangles*sizeof(IQMTriangle), 1, iqmFile); + memcpy(tri, fileDataPtr + iqmHeader->ofs_triangles, iqmHeader->num_triangles*sizeof(IQMTriangle)); + + for (int m = 0; m < model.meshCount; m++) + { + int tcounter = 0; + + for (unsigned int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++) + { + // IQM triangles indexes are stored in counter-clockwise, but raylib processes the index in linear order, + // expecting they point to the counter-clockwise vertex triangle, so we need to reverse triangle indexes + // NOTE: raylib renders vertex data in counter-clockwise order (standard convention) by default + model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex; + tcounter += 3; + } + } + + // Vertex arrays data processing + va = RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); + //fseek(iqmFile, iqmHeader->ofs_vertexarrays, SEEK_SET); + //fread(va, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray), 1, iqmFile); + memcpy(va, fileDataPtr + iqmHeader->ofs_vertexarrays, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); + + for (unsigned int i = 0; i < iqmHeader->num_vertexarrays; i++) + { + switch (va[i].type) + { + case IQM_POSITION: + { + vertex = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(vertex, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); + memcpy(vertex, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].vertices[vCounter] = vertex[i]; + model.meshes[m].animVertices[vCounter] = vertex[i]; + vCounter++; + } + } + } break; + case IQM_NORMAL: + { + normal = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(normal, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); + memcpy(normal, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].normals[vCounter] = normal[i]; + model.meshes[m].animNormals[vCounter] = normal[i]; + vCounter++; + } + } + } break; + case IQM_TEXCOORD: + { + text = RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(text, iqmHeader->num_vertexes*2*sizeof(float), 1, iqmFile); + memcpy(text, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*2*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++) + { + model.meshes[m].texcoords[vCounter] = text[i]; + vCounter++; + } + } + } break; + case IQM_BLENDINDEXES: + { + blendi = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(blendi, iqmHeader->num_vertexes*4*sizeof(char), 1, iqmFile); + memcpy(blendi, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(char)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int boneCounter = 0; + for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneIds[boneCounter] = blendi[i]; + boneCounter++; + } + } + } break; + case IQM_BLENDWEIGHTS: + { + blendw = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile); + memcpy(blendw, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int boneCounter = 0; + for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneWeights[boneCounter] = blendw[i]/255.0f; + boneCounter++; + } + } + } break; + } + } + + // Bones (joints) data processing + ijoint = RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint)); + //fseek(iqmFile, iqmHeader->ofs_joints, SEEK_SET); + //fread(ijoint, iqmHeader->num_joints*sizeof(IQMJoint), 1, iqmFile); + memcpy(ijoint, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint)); + + model.boneCount = iqmHeader->num_joints; + model.bones = RL_MALLOC(iqmHeader->num_joints*sizeof(BoneInfo)); + model.bindPose = RL_MALLOC(iqmHeader->num_joints*sizeof(Transform)); + + for (unsigned int i = 0; i < iqmHeader->num_joints; i++) + { + // Bones + model.bones[i].parent = ijoint[i].parent; + //fseek(iqmFile, iqmHeader->ofs_text + ijoint[i].name, SEEK_SET); + //fread(model.bones[i].name, BONE_NAME_LENGTH*sizeof(char), 1, iqmFile); + memcpy(model.bones[i].name, fileDataPtr + iqmHeader->ofs_text + ijoint[i].name, BONE_NAME_LENGTH*sizeof(char)); + + // Bind pose (base pose) + model.bindPose[i].translation.x = ijoint[i].translate[0]; + model.bindPose[i].translation.y = ijoint[i].translate[1]; + model.bindPose[i].translation.z = ijoint[i].translate[2]; + + model.bindPose[i].rotation.x = ijoint[i].rotate[0]; + model.bindPose[i].rotation.y = ijoint[i].rotate[1]; + model.bindPose[i].rotation.z = ijoint[i].rotate[2]; + model.bindPose[i].rotation.w = ijoint[i].rotate[3]; + + model.bindPose[i].scale.x = ijoint[i].scale[0]; + model.bindPose[i].scale.y = ijoint[i].scale[1]; + model.bindPose[i].scale.z = ijoint[i].scale[2]; + } + + // Build bind pose from parent joints + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent >= 0) + { + model.bindPose[i].rotation = QuaternionMultiply(model.bindPose[model.bones[i].parent].rotation, model.bindPose[i].rotation); + model.bindPose[i].translation = Vector3RotateByQuaternion(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].rotation); + model.bindPose[i].translation = Vector3Add(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].translation); + model.bindPose[i].scale = Vector3Multiply(model.bindPose[i].scale, model.bindPose[model.bones[i].parent].scale); + } + } + + RL_FREE(fileData); + + RL_FREE(imesh); + RL_FREE(tri); + RL_FREE(va); + RL_FREE(vertex); + RL_FREE(normal); + RL_FREE(text); + RL_FREE(blendi); + RL_FREE(blendw); + RL_FREE(ijoint); + + return model; +} + +// Load IQM animation data +static ModelAnimation* LoadIQMModelAnimations(const char* fileName, int* animCount) +{ +#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number +#define IQM_VERSION 2 // only IQM version 2 supported + + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + unsigned char *fileDataPtr = fileData; + + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return NULL; + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + return NULL; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + return NULL; + } + + // Get bones data + IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); + //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); + //fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile); + memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); + + // Get animations data + *animCount = iqmHeader->num_anims; + IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); + //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); + //fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile); + memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); + + ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); + + // frameposes + unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); + //fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile); + memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + + for (unsigned int a = 0; a < iqmHeader->num_anims; a++) + { + animations[a].frameCount = anim[a].num_frames; + animations[a].boneCount = iqmHeader->num_poses; + animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); + animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); + // animations[a].framerate = anim.framerate; // TODO: Use framerate? + + for (unsigned int j = 0; j < iqmHeader->num_poses; j++) + { + strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); + animations[a].bones[j].parent = poses[j].parent; + } + + for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); + + int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; + + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (unsigned int i = 0; i < iqmHeader->num_poses; i++) + { + animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation); + } + } + + // Build frameposes + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (int i = 0; i < animations[a].boneCount; i++) + { + if (animations[a].bones[i].parent >= 0) + { + animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation); + animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation); + animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation); + animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale); + } + } + } + } + + RL_FREE(fileData); + + RL_FREE(framedata); + RL_FREE(poses); + RL_FREE(anim); + + return animations; +} + +#endif + +#if defined(SUPPORT_FILEFORMAT_GLTF) + +static const unsigned char base64Table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51 +}; + +static int GetSizeBase64(char *input) +{ + int size = 0; + + for (int i = 0; input[4*i] != 0; i++) + { + if (input[4*i + 3] == '=') + { + if (input[4*i + 2] == '=') size += 1; + else size += 2; + } + else size += 3; + } + + return size; +} + +static unsigned char *DecodeBase64(char *input, int *size) +{ + *size = GetSizeBase64(input); + + unsigned char *buf = (unsigned char *)RL_MALLOC(*size); + for (int i = 0; i < *size/3; i++) + { + unsigned char a = base64Table[(int)input[4*i]]; + unsigned char b = base64Table[(int)input[4*i + 1]]; + unsigned char c = base64Table[(int)input[4*i + 2]]; + unsigned char d = base64Table[(int)input[4*i + 3]]; + + buf[3*i] = (a << 2) | (b >> 4); + buf[3*i + 1] = (b << 4) | (c >> 2); + buf[3*i + 2] = (c << 6) | d; + } + + if (*size%3 == 1) + { + int n = *size/3; + unsigned char a = base64Table[(int)input[4*n]]; + unsigned char b = base64Table[(int)input[4*n + 1]]; + buf[*size - 1] = (a << 2) | (b >> 4); + } + else if (*size%3 == 2) + { + int n = *size/3; + unsigned char a = base64Table[(int)input[4*n]]; + unsigned char b = base64Table[(int)input[4*n + 1]]; + unsigned char c = base64Table[(int)input[4*n + 2]]; + buf[*size - 2] = (a << 2) | (b >> 4); + buf[*size - 1] = (b << 4) | (c >> 2); + } + return buf; +} + +// Load texture from cgltf_image +static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Color tint) +{ + Image rimage = { 0 }; + + if (image->uri) + { + if ((strlen(image->uri) > 5) && + (image->uri[0] == 'd') && + (image->uri[1] == 'a') && + (image->uri[2] == 't') && + (image->uri[3] == 'a') && + (image->uri[4] == ':')) + { + // Data URI + // Format: data:;base64, + + // Find the comma + int i = 0; + while ((image->uri[i] != ',') && (image->uri[i] != 0)) i++; + + if (image->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image"); + else + { + int size = 0; + unsigned char *data = DecodeBase64(image->uri + i + 1, &size); + + int width, height; + unsigned char *raw = stbi_load_from_memory(data, size, &width, &height, NULL, 4); + RL_FREE(data); + + rimage.data = raw; + rimage.width = width; + rimage.height = height; + rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + rimage.mipmaps = 1; + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + } + } + else + { + rimage = LoadImage(TextFormat("%s/%s", texPath, image->uri)); + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + } + } + else if (image->buffer_view) + { + unsigned char *data = RL_MALLOC(image->buffer_view->size); + int n = (int)image->buffer_view->offset; + int stride = (int)image->buffer_view->stride ? (int)image->buffer_view->stride : 1; + + for (unsigned int i = 0; i < image->buffer_view->size; i++) + { + data[i] = ((unsigned char *)image->buffer_view->buffer->data)[n]; + n += stride; + } + + int width, height; + unsigned char *raw = stbi_load_from_memory(data, (int)image->buffer_view->size, &width, &height, NULL, 4); + RL_FREE(data); + + rimage.data = raw; + rimage.width = width; + rimage.height = height; + rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + rimage.mipmaps = 1; + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + } + else rimage = GenImageColor(1, 1, tint); + + return rimage; +} + + +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable, unsigned int elements, unsigned int size) +{ + if (acc->count == 2) + { + if (index > 1) return false; + + memcpy(variable, index == 0 ? acc->min : acc->max, elements*size); + return true; + } + + unsigned int stride = size*elements; + memset(variable, 0, stride); + + if (acc->buffer_view == NULL || acc->buffer_view->buffer == NULL || acc->buffer_view->buffer->data == NULL) return false; + + void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index*stride) + acc->buffer_view->offset + acc->offset; + memcpy(variable, readPosition, stride); + return true; +} + +// LoadGLTF loads in model data from given filename, supporting both .gltf and .glb +static Model LoadGLTF(const char *fileName) +{ + /*********************************************************************************** + + Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) and Hristo Stamenov(@object71) + + Features: + - Supports .gltf and .glb files + - Supports embedded (base64) or external textures + - Loads all raylib supported material textures, values and colors + - Supports multiple mesh per model and multiple primitives per model + + Some restrictions (not exhaustive): + - Triangle-only meshes + - Not supported node hierarchies or transforms + - Only supports unsigned short indices (no byte/unsigned int) + - Only supports float for texture coordinates (no byte/unsigned short) + + *************************************************************************************/ + + Model model = { 0 }; + + // glTF file loading + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData == NULL) return model; + + // glTF data loading + cgltf_options options = { 0 }; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + + if (result == cgltf_result_success) + { + TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count); + TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->materials_count); + + // Read data buffers + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); + + int primitivesCount = 0; + + for (unsigned int i = 0; i < data->meshes_count; i++) + primitivesCount += (int)data->meshes[i].primitives_count; + + // Process glTF data and map to model + model.meshCount = primitivesCount; + model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.materialCount = (int)data->materials_count + 1; + model.materials = RL_MALLOC(model.materialCount*sizeof(Material)); + model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int)); + model.boneCount = (int)data->nodes_count; + model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo)); + model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform)); + + InitGLTFBones(&model, data); + LoadGLTFMaterial(&model, fileName, data); + + int primitiveIndex = 0; + + for (unsigned int i = 0; i < data->meshes_count; i++) + { + for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++) + { + for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++) + { + if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + model.meshes[primitiveIndex].vertexCount = (int)acc->count; + int bufferSize = model.meshes[primitiveIndex].vertexCount*3*sizeof(float); + model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize); + model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].vertices + (a*3), 3, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + int readValue[3]; + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].vertices[(a*3) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].vertices[(a*3) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].vertices[(a*3) + 2] = (float)readValue[2]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short vertices + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF vertices must be float or int", fileName); + } + + memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + int bufferSize = (int)(acc->count*3*sizeof(float)); + model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize); + model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].normals + (a*3), 3, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + int readValue[3]; + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].normals[(a*3) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].normals[(a*3) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].normals[(a*3) + 2] = (float)readValue[2]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short normals + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); + } + + memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + if (acc->component_type == cgltf_component_type_r_32f) + { + model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float)); + + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a*2), 2, sizeof(float)); + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short texture coordinates + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); + } + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + LoadGLTFBoneAttribute(&model, acc, data, primitiveIndex); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float)); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].boneWeights + (a*4), 4, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + unsigned int readValue[4]; + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int)); + model.meshes[primitiveIndex].normals[(a*4) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].normals[(a*4) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].normals[(a*4) + 2] = (float)readValue[2]; + model.meshes[primitiveIndex].normals[(a*4) + 3] = (float)readValue[3]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short weights + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); + } + } + } + + cgltf_accessor *acc = data->meshes[i].primitives[p].indices; + LoadGLTFModelIndices(&model, acc, primitiveIndex); + + if (data->meshes[i].primitives[p].material) + { + // Compute the offset + model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials); + } + else + { + model.meshMaterial[primitiveIndex] = model.materialCount - 1; + } + + BindGLTFPrimitiveToBones(&model, data, primitiveIndex); + + primitiveIndex++; + } + + } + + cgltf_free(data); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); + + RL_FREE(fileData); + + return model; +} + +static void InitGLTFBones(Model* model, const cgltf_data* data) +{ + for (unsigned int j = 0; j < data->nodes_count; j++) + { + strcpy(model->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); + model->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1; + } + + for (unsigned int i = 0; i < data->nodes_count; i++) + { + if (data->nodes[i].has_translation) memcpy(&model->bindPose[i].translation, data->nodes[i].translation, 3*sizeof(float)); + else model->bindPose[i].translation = Vector3Zero(); + + if (data->nodes[i].has_rotation) memcpy(&model->bindPose[i].rotation, data->nodes[i].rotation, 4*sizeof(float)); + else model->bindPose[i].rotation = QuaternionIdentity(); + + model->bindPose[i].rotation = QuaternionNormalize(model->bindPose[i].rotation); + + if (data->nodes[i].has_scale) memcpy(&model->bindPose[i].scale, data->nodes[i].scale, 3*sizeof(float)); + else model->bindPose[i].scale = Vector3One(); + } + + { + bool* completedBones = RL_CALLOC(model->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < model->boneCount) { + for (int i = 0; i < model->boneCount; i++) + { + if (completedBones[i]) continue; + + if (model->bones[i].parent < 0) { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[model->bones[i].parent]) continue; + + Transform* currentTransform = &model->bindPose[i]; + BoneInfo* currentBone = &model->bones[i]; + int root = currentBone->parent; + if (root >= model->boneCount) + root = 0; + Transform* parentTransform = &model->bindPose[root]; + + currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); + currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); + currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation); + currentTransform->scale = Vector3Multiply(currentTransform->scale, parentTransform->scale); + completedBones[i] = true; + numberCompletedBones++; + } + } + + RL_FREE(completedBones); + } +} + +static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data) +{ + for (int i = 0; i < model->materialCount - 1; i++) + { + model->materials[i] = LoadMaterialDefault(); + Color tint = (Color){ 255, 255, 255, 255 }; + const char *texPath = GetDirectoryPath(fileName); + + // Ensure material follows raylib support for PBR (metallic/roughness flow) + if (data->materials[i].has_pbr_metallic_roughness) + { + tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); + tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); + tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); + tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); + + model->materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint; + + if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) + { + Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo); + UnloadImage(albedo); + } + + tint = WHITE; // Set tint to white after it's been used by Albedo + + if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) + { + Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); + + float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; + + float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; + model->materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic; + + UnloadImage(metallicRoughness); + } + + if (data->materials[i].normal_texture.texture) + { + Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage); + UnloadImage(normalImage); + } + + if (data->materials[i].occlusion_texture.texture) + { + Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); + UnloadImage(occulsionImage); + } + + if (data->materials[i].emissive_texture.texture) + { + Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); + tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); + tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); + tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); + model->materials[i].maps[MATERIAL_MAP_EMISSION].color = tint; + UnloadImage(emissiveImage); + } + } + } + + model->materials[model->materialCount - 1] = LoadMaterialDefault(); +} + +static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex) +{ + if (jointsAccessor->component_type == cgltf_component_type_r_16u) + { + model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int)*jointsAccessor->count*4); + short* bones = RL_MALLOC(sizeof(short)*jointsAccessor->count*4); + + for (int a = 0; a < jointsAccessor->count; a++) + { + GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(short)); + } + + for (unsigned int a = 0; a < jointsAccessor->count*4; a++) + { + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (&(data->nodes[k]) == skinJoint) + { + model->meshes[primitiveIndex].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else if (jointsAccessor->component_type == cgltf_component_type_r_8u) + { + model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int)*jointsAccessor->count*4); + unsigned char* bones = RL_MALLOC(sizeof(unsigned char)*jointsAccessor->count*4); + + for (int a = 0; a < jointsAccessor->count; a++) + { + GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(unsigned char)); + } + + for (unsigned int a = 0; a < jointsAccessor->count*4; a++) + { + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (&(data->nodes[k]) == skinJoint) + { + model->meshes[primitiveIndex].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else + { + // TODO: Support other size of bone index? + TRACELOG(LOG_WARNING, "MODEL: glTF bones in unexpected format"); + } +} + +static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex) +{ + if (model->meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0) + { + for (int nodeId = 0; nodeId < data->nodes_count; nodeId++) + { + if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) + { + model->meshes[primitiveIndex].boneIds = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(int)); + model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(float)); + + for (int b = 0; b < 4*model->meshes[primitiveIndex].vertexCount; b++) + { + if (b%4 == 0) + { + model->meshes[primitiveIndex].boneIds[b] = nodeId; + model->meshes[primitiveIndex].boneWeights[b] = 1.0f; + } + else + { + model->meshes[primitiveIndex].boneIds[b] = 0; + model->meshes[primitiveIndex].boneWeights[b] = 0.0f; + } + + } + + Vector3 boundVertex = { 0 }; + Vector3 boundNormal = { 0 }; + + Vector3 outTranslation = { 0 }; + Quaternion outRotation = { 0 }; + Vector3 outScale = { 0 }; + + int vCounter = 0; + int boneCounter = 0; + int boneId = 0; + + for (int i = 0; i < model->meshes[primitiveIndex].vertexCount; i++) + { + boneId = model->meshes[primitiveIndex].boneIds[boneCounter]; + outTranslation = model->bindPose[boneId].translation; + outRotation = model->bindPose[boneId].rotation; + outScale = model->bindPose[boneId].scale; + + // Vertices processing + boundVertex = (Vector3){ model->meshes[primitiveIndex].vertices[vCounter], model->meshes[primitiveIndex].vertices[vCounter + 1], model->meshes[primitiveIndex].vertices[vCounter + 2] }; + boundVertex = Vector3Multiply(boundVertex, outScale); + boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation); + boundVertex = Vector3Add(boundVertex, outTranslation); + model->meshes[primitiveIndex].vertices[vCounter] = boundVertex.x; + model->meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y; + model->meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z; + + // Normals processing + if (model->meshes[primitiveIndex].normals != NULL) + { + boundNormal = (Vector3){ model->meshes[primitiveIndex].normals[vCounter], model->meshes[primitiveIndex].normals[vCounter + 1], model->meshes[primitiveIndex].normals[vCounter + 2] }; + boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation); + model->meshes[primitiveIndex].normals[vCounter] = boundNormal.x; + model->meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y; + model->meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z; + } + + vCounter += 3; + boneCounter += 4; + } + } + } + } +} + +static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex) +{ + if (indexAccessor) + { + if (indexAccessor->component_type == cgltf_component_type_r_16u || indexAccessor->component_type == cgltf_component_type_r_16) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned short readValue = 0; + for (int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(short)); + model->meshes[primitiveIndex].indices[a] = readValue; + } + } + else if (indexAccessor->component_type == cgltf_component_type_r_8u || indexAccessor->component_type == cgltf_component_type_r_8) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned char readValue = 0; + for (int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(char)); + model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; + } + } + else if (indexAccessor->component_type == cgltf_component_type_r_32u) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned int readValue; + for (int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(unsigned int)); + model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; + } + } + } + else + { + // Unindexed mesh + model->meshes[primitiveIndex].triangleCount = model->meshes[primitiveIndex].vertexCount/3; + } +} + +// LoadGLTF loads in animation data from given filename +static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount) +{ + /*********************************************************************************** + + Function implemented by Hristo Stamenov (@object71) + + Features: + - Supports .gltf and .glb files + + Some restrictions (not exhaustive): + - ... + + *************************************************************************************/ + + // glTF file loading + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + ModelAnimation *animations = NULL; + + if (fileData == NULL) return animations; + + // glTF data loading + cgltf_options options = { 0 }; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + + if (result == cgltf_result_success) + { + TRACELOG(LOG_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" : + "gltf", data->animations_count); + + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_WARNING, "MODEL: [%s] unable to load glTF animations data", fileName); + animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation)); + *animCount = (int)data->animations_count; + + for (unsigned int a = 0; a < data->animations_count; a++) + { + // gltf animation consists of the following structures: + // - nodes - bones + // - channels - single transformation type on a single bone + // - node - bone + // - transformation type (path) - translation, rotation, scale + // - sampler - animation samples + // - input - points in time this transformation happens + // - output - the transformation amount at the given input points in time + // - interpolation - the type of interpolation to use between the frames + + cgltf_animation *animation = data->animations + a; + + ModelAnimation *output = animations + a; + + // 30 frames sampled per second + const float timeStep = (1.0f/30.0f); + float animationDuration = 0.0f; + + // Getting the max animation time to consider for animation duration + for (unsigned int i = 0; i < animation->channels_count; i++) + { + cgltf_animation_channel* channel = animation->channels + i; + int frameCounts = (int)channel->sampler->input->count; + float lastFrameTime = 0.0f; + + if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float))) + { + animationDuration = fmaxf(lastFrameTime, animationDuration); + } + } + + output->frameCount = (int)(animationDuration / timeStep); + output->boneCount = (int)data->nodes_count; + output->bones = RL_MALLOC(output->boneCount*sizeof(BoneInfo)); + output->framePoses = RL_MALLOC(output->frameCount*sizeof(Transform *)); + // output->framerate = // TODO: Use framerate instead of const timestep + + // Name and parent bones + for (unsigned int j = 0; j < data->nodes_count; j++) + { + strcpy(output->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); + output->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1; + } + + // Allocate data for frames + // Initiate with zero bone translations + for (int frame = 0; frame < output->frameCount; frame++) + { + output->framePoses[frame] = RL_MALLOC(output->frameCount*data->nodes_count*sizeof(Transform)); + + for (unsigned int i = 0; i < data->nodes_count; i++) + { + output->framePoses[frame][i].translation = Vector3Zero(); + output->framePoses[frame][i].rotation = QuaternionIdentity(); + output->framePoses[frame][i].rotation = QuaternionNormalize(output->framePoses[frame][i].rotation); + output->framePoses[frame][i].scale = Vector3One(); + } + } + + // for each single transformation type on single bone + for (unsigned int channelId = 0; channelId < animation->channels_count; channelId++) + { + cgltf_animation_channel* channel = animation->channels + channelId; + cgltf_animation_sampler* sampler = channel->sampler; + + int boneId = (int)(channel->target_node - data->nodes); + + for (int frame = 0; frame < output->frameCount; frame++) + { + bool shouldSkipFurtherTransformation = true; + int outputMin = 0; + int outputMax = 0; + float frameTime = frame*timeStep; + float lerpPercent = 0.0f; + + // For this transformation: + // getting between which input values the current frame time position + // and also what is the percent to use in the linear interpolation later + for (unsigned int j = 0; j < sampler->input->count; j++) + { + float inputFrameTime; + if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float))) + { + if (frameTime < inputFrameTime) + { + shouldSkipFurtherTransformation = false; + outputMin = (j == 0) ? 0 : j - 1; + outputMax = j; + + float previousInputTime = 0.0f; + if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float))) + { + if ((inputFrameTime - previousInputTime) != 0) + { + lerpPercent = (frameTime - previousInputTime)/(inputFrameTime - previousInputTime); + } + } + + break; + } + } + else break; + } + + // If the current transformation has no information for the current frame time point + if (shouldSkipFurtherTransformation) continue; + + if (channel->target_path == cgltf_animation_path_type_translation) + { + Vector3 translationStart; + Vector3 translationEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success; + + if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent); + } + if (channel->target_path == cgltf_animation_path_type_rotation) + { + Quaternion rotationStart; + Quaternion rotationEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success; + + if (success) + { + output->framePoses[frame][boneId].rotation = QuaternionLerp(rotationStart, rotationEnd, lerpPercent); + output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation); + } + } + if (channel->target_path == cgltf_animation_path_type_scale) + { + Vector3 scaleStart; + Vector3 scaleEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success; + + if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent); + } + } + } + + // Build frameposes + for (int frame = 0; frame < output->frameCount; frame++) + { + bool *completedBones = RL_CALLOC(output->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < output->boneCount) + { + for (int i = 0; i < output->boneCount; i++) + { + if (completedBones[i]) continue; + + if (output->bones[i].parent < 0) + { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[output->bones[i].parent]) continue; + + output->framePoses[frame][i].rotation = QuaternionMultiply(output->framePoses[frame][output->bones[i].parent].rotation, output->framePoses[frame][i].rotation); + output->framePoses[frame][i].translation = Vector3RotateByQuaternion(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].rotation); + output->framePoses[frame][i].translation = Vector3Add(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].translation); + output->framePoses[frame][i].scale = Vector3Multiply(output->framePoses[frame][i].scale, output->framePoses[frame][output->bones[i].parent].scale); + completedBones[i] = true; + numberCompletedBones++; + } + } + + RL_FREE(completedBones); + } + + } + + cgltf_free(data); + } + else TRACELOG(LOG_WARNING, ": [%s] Failed to load glTF data", fileName); + + RL_FREE(fileData); + + return animations; +} + +#endif diff --git a/raylib/physac.h b/raylib/physac.h new file mode 100644 index 0000000..676a969 --- /dev/null +++ b/raylib/physac.h @@ -0,0 +1,1988 @@ +/********************************************************************************************** +* +* Physac v1.1 - 2D Physics library for videogames +* +* DESCRIPTION: +* +* Physac is a small 2D physics engine written in pure C. The engine uses a fixed time-step thread loop +* to simluate physics. A physics step contains the following phases: get collision information, +* apply dynamics, collision solving and position correction. It uses a very simple struct for physic +* bodies with a position vector to be used in any 3D rendering API. +* +* CONFIGURATION: +* +* #define PHYSAC_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define PHYSAC_STATIC (defined by default) +* The generated implementation will stay private inside implementation file and all +* internal symbols and functions will only be visible inside that file. +* +* #define PHYSAC_DEBUG +* Show debug traces log messages about physic bodies creation/destruction, physic system errors, +* some calculations results and NULL reference exceptions +* +* #define PHYSAC_DEFINE_VECTOR2_TYPE +* Forces library to define struct Vector2 data type (float x; float y) +* +* #define PHYSAC_AVOID_TIMMING_SYSTEM +* Disables internal timming system, used by UpdatePhysics() to launch timmed physic steps, +* it allows just running UpdatePhysics() automatically on a separate thread at a desired time step. +* In case physics steps update needs to be controlled by user with a custom timming mechanism, +* just define this flag and the internal timming mechanism will be avoided, in that case, +* timming libraries are neither required by the module. +* +* #define PHYSAC_MALLOC() +* #define PHYSAC_CALLOC() +* #define PHYSAC_FREE() +* You can define your own malloc/free implementation replacing stdlib.h malloc()/free() functions. +* Otherwise it will include stdlib.h and use the C standard library malloc()/free() function. +* +* COMPILATION: +* +* Use the following code to compile with GCC: +* gcc -o $(NAME_PART).exe $(FILE_NAME) -s -static -lraylib -lopengl32 -lgdi32 -lwinmm -std=c99 +* +* VERSIONS HISTORY: +* 1.1 (20-Jan-2021) @raysan5: Library general revision +* Removed threading system (up to the user) +* Support MSVC C++ compilation using CLITERAL() +* Review DEBUG mechanism for TRACELOG() and all TRACELOG() messages +* Review internal variables/functions naming for consistency +* Allow option to avoid internal timming system, to allow app manage the steps +* 1.0 (12-Jun-2017) First release of the library +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2016-2021 Victor Fisac (@victorfisac) and Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if !defined(PHYSAC_H) +#define PHYSAC_H + +#if defined(PHYSAC_STATIC) + #define PHYSACDEF static // Functions just visible to module including this file +#else + #if defined(__cplusplus) + #define PHYSACDEF extern "C" // Functions visible from other files (no name mangling of functions in C++) + #else + #define PHYSACDEF extern // Functions visible from other files + #endif +#endif + +// Allow custom memory allocators +#ifndef PHYSAC_MALLOC + #define PHYSAC_MALLOC(size) malloc(size) +#endif +#ifndef PHYSAC_CALLOC + #define PHYSAC_CALLOC(size, n) calloc(size, n) +#endif +#ifndef PHYSAC_FREE + #define PHYSAC_FREE(ptr) free(ptr) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define PHYSAC_MAX_BODIES 64 // Maximum number of physic bodies supported +#define PHYSAC_MAX_MANIFOLDS 4096 // Maximum number of physic bodies interactions (64x64) +#define PHYSAC_MAX_VERTICES 24 // Maximum number of vertex for polygons shapes +#define PHYSAC_DEFAULT_CIRCLE_VERTICES 24 // Default number of vertices for circle shapes + +#define PHYSAC_COLLISION_ITERATIONS 100 +#define PHYSAC_PENETRATION_ALLOWANCE 0.05f +#define PHYSAC_PENETRATION_CORRECTION 0.4f + +#define PHYSAC_PI 3.14159265358979323846f +#define PHYSAC_DEG2RAD (PHYSAC_PI/180.0f) + +//---------------------------------------------------------------------------------- +// Data Types Structure Definition +//---------------------------------------------------------------------------------- +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#endif + +typedef enum PhysicsShapeType { PHYSICS_CIRCLE = 0, PHYSICS_POLYGON } PhysicsShapeType; + +// Previously defined to be used in PhysicsShape struct as circular dependencies +typedef struct PhysicsBodyData *PhysicsBody; + +#if defined(PHYSAC_DEFINE_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#endif + +// Matrix2x2 type (used for polygon shape rotation matrix) +typedef struct Matrix2x2 { + float m00; + float m01; + float m10; + float m11; +} Matrix2x2; + +typedef struct PhysicsVertexData { + unsigned int vertexCount; // Vertex count (positions and normals) + Vector2 positions[PHYSAC_MAX_VERTICES]; // Vertex positions vectors + Vector2 normals[PHYSAC_MAX_VERTICES]; // Vertex normals vectors +} PhysicsVertexData; + +typedef struct PhysicsShape { + PhysicsShapeType type; // Shape type (circle or polygon) + PhysicsBody body; // Shape physics body data pointer + PhysicsVertexData vertexData; // Shape vertices data (used for polygon shapes) + float radius; // Shape radius (used for circle shapes) + Matrix2x2 transform; // Vertices transform matrix 2x2 +} PhysicsShape; + +typedef struct PhysicsBodyData { + unsigned int id; // Unique identifier + bool enabled; // Enabled dynamics state (collisions are calculated anyway) + Vector2 position; // Physics body shape pivot + Vector2 velocity; // Current linear velocity applied to position + Vector2 force; // Current linear force (reset to 0 every step) + float angularVelocity; // Current angular velocity applied to orient + float torque; // Current angular force (reset to 0 every step) + float orient; // Rotation in radians + float inertia; // Moment of inertia + float inverseInertia; // Inverse value of inertia + float mass; // Physics body mass + float inverseMass; // Inverse value of mass + float staticFriction; // Friction when the body has not movement (0 to 1) + float dynamicFriction; // Friction when the body has movement (0 to 1) + float restitution; // Restitution coefficient of the body (0 to 1) + bool useGravity; // Apply gravity force to dynamics + bool isGrounded; // Physics grounded on other body state + bool freezeOrient; // Physics rotation constraint + PhysicsShape shape; // Physics body shape information (type, radius, vertices, transform) +} PhysicsBodyData; + +typedef struct PhysicsManifoldData { + unsigned int id; // Unique identifier + PhysicsBody bodyA; // Manifold first physics body reference + PhysicsBody bodyB; // Manifold second physics body reference + float penetration; // Depth of penetration from collision + Vector2 normal; // Normal direction vector from 'a' to 'b' + Vector2 contacts[2]; // Points of contact during collision + unsigned int contactsCount; // Current collision number of contacts + float restitution; // Mixed restitution during collision + float dynamicFriction; // Mixed dynamic friction during collision + float staticFriction; // Mixed static friction during collision +} PhysicsManifoldData, *PhysicsManifold; + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// Physics system management +PHYSACDEF void InitPhysics(void); // Initializes physics system +PHYSACDEF void UpdatePhysics(void); // Update physics system +PHYSACDEF void ResetPhysics(void); // Reset physics system (global variables) +PHYSACDEF void ClosePhysics(void); // Close physics system and unload used memory +PHYSACDEF void SetPhysicsTimeStep(double delta); // Sets physics fixed time step in milliseconds. 1.666666 by default +PHYSACDEF void SetPhysicsGravity(float x, float y); // Sets physics global gravity force + +// Physic body creation/destroy +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density); // Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density); // Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density); // Creates a new polygon physics body with generic parameters +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body); // Destroy a physics body + +// Physic body forces +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force); // Adds a force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount); // Adds an angular force to a physics body +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force); // Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians); // Sets physics body shape transform based on radians parameter + +// Query physics info +PHYSACDEF PhysicsBody GetPhysicsBody(int index); // Returns a physics body of the bodies pool at a specific index +PHYSACDEF int GetPhysicsBodiesCount(void); // Returns the current amount of created physics bodies +PHYSACDEF int GetPhysicsShapeType(int index); // Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeVerticesCount(int index); // Returns the amount of vertices of a physics body shape +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex); // Returns transformed position of a body shape (body position + vertex transformed position) + +#if defined(__cplusplus) +} +#endif + +#endif // PHYSAC_H + +/*********************************************************************************** +* +* PHYSAC IMPLEMENTATION +* +************************************************************************************/ + +#if defined(PHYSAC_IMPLEMENTATION) + +// Support TRACELOG macros +#if defined(PHYSAC_DEBUG) + #include // Required for: printf() + #define TRACELOG(...) printf(__VA_ARGS__) +#else + #define TRACELOG(...) (void)0; +#endif + +#include // Required for: malloc(), calloc(), free() +#include // Required for: cosf(), sinf(), fabs(), sqrtf() + +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) + // Time management functionality + #include // Required for: time(), clock_gettime() + #if defined(_WIN32) + // Functions required to query time on Windows + int __stdcall QueryPerformanceCounter(unsigned long long int *lpPerformanceCount); + int __stdcall QueryPerformanceFrequency(unsigned long long int *lpFrequency); + #endif + #if defined(__linux__) || defined(__FreeBSD__) + #if _POSIX_C_SOURCE < 199309L + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. + #endif + #include // Required for: timespec + #endif + #if defined(__APPLE__) // macOS also defines __MACH__ + #include // Required for: mach_absolute_time() + #endif +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized from { } initializers. +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define PHYSAC_MIN(a,b) (((a)<(b))?(a):(b)) +#define PHYSAC_MAX(a,b) (((a)>(b))?(a):(b)) +#define PHYSAC_FLT_MAX 3.402823466e+38f +#define PHYSAC_EPSILON 0.000001f +#define PHYSAC_K 1.0f/3.0f +#define PHYSAC_VECTOR_ZERO CLITERAL(Vector2){ 0.0f, 0.0f } + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static double deltaTime = 1.0/60.0/10.0 * 1000; // Delta time in milliseconds used for physics steps + +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) +// Time measure variables +static double baseClockTicks = 0.0; // Offset clock ticks for MONOTONIC clock +static unsigned long long int frequency = 0; // Hi-res clock frequency +static double startTime = 0.0; // Start time in milliseconds +static double currentTime = 0.0; // Current time in milliseconds +#endif + +// Physics system configuration +static PhysicsBody bodies[PHYSAC_MAX_BODIES]; // Physics bodies pointers array +static unsigned int physicsBodiesCount = 0; // Physics world current bodies counter +static PhysicsManifold contacts[PHYSAC_MAX_MANIFOLDS]; // Physics bodies pointers array +static unsigned int physicsManifoldsCount = 0; // Physics world current manifolds counter + +static Vector2 gravityForce = { 0.0f, 9.81f }; // Physics world gravity force + +// Utilities variables +static unsigned int usedMemory = 0; // Total allocated dynamic memory + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) +// Timming measure functions +static void InitTimer(void); // Initializes hi-resolution MONOTONIC timer +static unsigned long long int GetClockTicks(void); // Get hi-res MONOTONIC time measure in mseconds +static double GetCurrentTime(void); // Get current time measure in milliseconds +#endif + +static void UpdatePhysicsStep(void); // Update physics step (dynamics, collisions and position corrections) + +static int FindAvailableBodyIndex(); // Finds a valid index for a new physics body initialization +static int FindAvailableManifoldIndex(); // Finds a valid index for a new manifold initialization +static PhysicsVertexData CreateDefaultPolygon(float radius, int sides); // Creates a random polygon shape with max vertex distance from polygon pivot +static PhysicsVertexData CreateRectanglePolygon(Vector2 pos, Vector2 size); // Creates a rectangle polygon shape based on a min and max positions + +static void InitializePhysicsManifolds(PhysicsManifold manifold); // Initializes physics manifolds to solve collisions +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b); // Creates a new physics manifold to solve collision +static void DestroyPhysicsManifold(PhysicsManifold manifold); // Unitializes and destroys a physics manifold + +static void SolvePhysicsManifold(PhysicsManifold manifold); // Solves a created physics manifold between two physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold); // Solves collision between two circle shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold); // Solves collision between a circle to a polygon shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold); // Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold); // Solves collision between two polygons shape physics bodies +static void IntegratePhysicsForces(PhysicsBody body); // Integrates physics forces into velocity +static void IntegratePhysicsVelocity(PhysicsBody body); // Integrates physics velocity into position and forces +static void IntegratePhysicsImpulses(PhysicsManifold manifold); // Integrates physics collisions impulses to solve collisions +static void CorrectPhysicsPositions(PhysicsManifold manifold); // Corrects physics bodies positions based on manifolds collision information +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index); // Finds two polygon shapes incident face +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB); // Finds polygon shapes axis least penetration + +// Math required functions +static Vector2 MathVector2Product(Vector2 vector, float value); // Returns the product of a vector and a value +static float MathVector2CrossProduct(Vector2 v1, Vector2 v2); // Returns the cross product of two vectors +static float MathVector2SqrLen(Vector2 vector); // Returns the len square root of a vector +static float MathVector2DotProduct(Vector2 v1, Vector2 v2); // Returns the dot product of two vectors +static inline float MathVector2SqrDistance(Vector2 v1, Vector2 v2); // Returns the square root of distance between two vectors +static void MathVector2Normalize(Vector2 *vector); // Returns the normalized values of a vector +static Vector2 MathVector2Add(Vector2 v1, Vector2 v2); // Returns the sum of two given vectors +static Vector2 MathVector2Subtract(Vector2 v1, Vector2 v2); // Returns the subtract of two given vectors +static Matrix2x2 MathMatFromRadians(float radians); // Returns a matrix 2x2 from a given radians value +static inline Matrix2x2 MathMatTranspose(Matrix2x2 matrix); // Returns the transpose of a given matrix 2x2 +static inline Vector2 MathMatVector2Product(Matrix2x2 matrix, Vector2 vector); // Returns product between matrix 2x2 and vector +static int MathVector2Clip(Vector2 normal, Vector2 *faceA, Vector2 *faceB, float clip); // Returns clipping value based on a normal and two faces +static Vector2 MathTriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3); // Returns the barycenter of a triangle given by 3 points + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Initializes physics values, pointers and creates physics loop thread +PHYSACDEF void InitPhysics(void) +{ +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) + // Initialize high resolution timer + InitTimer(); +#endif + + TRACELOG("[PHYSAC] Physics module initialized successfully\n"); +} + +// Sets physics global gravity force +PHYSACDEF void SetPhysicsGravity(float x, float y) +{ + gravityForce.x = x; + gravityForce.y = y; +} + +// Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density) +{ + PhysicsBody body = CreatePhysicsBodyPolygon(pos, radius, PHYSAC_DEFAULT_CIRCLE_VERTICES, density); + return body; +} + +// Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density) +{ + // NOTE: Make sure body data is initialized to 0 + PhysicsBody body = (PhysicsBody)PHYSAC_CALLOC(sizeof(PhysicsBodyData), 1); + usedMemory += sizeof(PhysicsBodyData); + + int id = FindAvailableBodyIndex(); + if (id != -1) + { + // Initialize new body with generic values + body->id = id; + body->enabled = true; + body->position = pos; + body->shape.type = PHYSICS_POLYGON; + body->shape.body = body; + body->shape.transform = MathMatFromRadians(0.0f); + body->shape.vertexData = CreateRectanglePolygon(pos, CLITERAL(Vector2){ width, height }); + + // Calculate centroid and moment of inertia + Vector2 center = { 0.0f, 0.0f }; + float area = 0.0f; + float inertia = 0.0f; + + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = body->shape.vertexData.positions[i]; + unsigned int nextIndex = (((i + 1) < body->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 p2 = body->shape.vertexData.positions[nextIndex]; + + float D = MathVector2CrossProduct(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*PHYSAC_K*(p1.x + p2.x); + center.y += triangleArea*PHYSAC_K*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*PHYSAC_K*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + body->shape.vertexData.positions[i].x -= center.x; + body->shape.vertexData.positions[i].y -= center.y; + } + + body->mass = density*area; + body->inverseMass = ((body->mass != 0.0f) ? 1.0f/body->mass : 0.0f); + body->inertia = density*inertia; + body->inverseInertia = ((body->inertia != 0.0f) ? 1.0f/body->inertia : 0.0f); + body->staticFriction = 0.4f; + body->dynamicFriction = 0.2f; + body->restitution = 0.0f; + body->useGravity = true; + body->isGrounded = false; + body->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = body; + physicsBodiesCount++; + + TRACELOG("[PHYSAC] Physic body created successfully (id: %i)\n", body->id); + } + else TRACELOG("[PHYSAC] Physic body could not be created, PHYSAC_MAX_BODIES reached\n"); + + return body; +} + +// Creates a new polygon physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density) +{ + PhysicsBody body = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int id = FindAvailableBodyIndex(); + if (id != -1) + { + // Initialize new body with generic values + body->id = id; + body->enabled = true; + body->position = pos; + body->velocity = PHYSAC_VECTOR_ZERO; + body->force = PHYSAC_VECTOR_ZERO; + body->angularVelocity = 0.0f; + body->torque = 0.0f; + body->orient = 0.0f; + body->shape.type = PHYSICS_POLYGON; + body->shape.body = body; + body->shape.transform = MathMatFromRadians(0.0f); + body->shape.vertexData = CreateDefaultPolygon(radius, sides); + + // Calculate centroid and moment of inertia + Vector2 center = { 0.0f, 0.0f }; + float area = 0.0f; + float inertia = 0.0f; + + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 position1 = body->shape.vertexData.positions[i]; + unsigned int nextIndex = (((i + 1) < body->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 position2 = body->shape.vertexData.positions[nextIndex]; + + float cross = MathVector2CrossProduct(position1, position2); + float triangleArea = cross/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*PHYSAC_K*(position1.x + position2.x); + center.y += triangleArea*PHYSAC_K*(position1.y + position2.y); + + float intx2 = position1.x*position1.x + position2.x*position1.x + position2.x*position2.x; + float inty2 = position1.y*position1.y + position2.y*position1.y + position2.y*position2.y; + inertia += (0.25f*PHYSAC_K*cross)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + body->shape.vertexData.positions[i].x -= center.x; + body->shape.vertexData.positions[i].y -= center.y; + } + + body->mass = density*area; + body->inverseMass = ((body->mass != 0.0f) ? 1.0f/body->mass : 0.0f); + body->inertia = density*inertia; + body->inverseInertia = ((body->inertia != 0.0f) ? 1.0f/body->inertia : 0.0f); + body->staticFriction = 0.4f; + body->dynamicFriction = 0.2f; + body->restitution = 0.0f; + body->useGravity = true; + body->isGrounded = false; + body->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = body; + physicsBodiesCount++; + + TRACELOG("[PHYSAC] Physic body created successfully (id: %i)\n", body->id); + } + else TRACELOG("[PHYSAC] Physics body could not be created, PHYSAC_MAX_BODIES reached\n"); + + return body; +} + +// Adds a force to a physics body +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force) +{ + if (body != NULL) body->force = MathVector2Add(body->force, force); +} + +// Adds an angular force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount) +{ + if (body != NULL) body->torque += amount; +} + +// Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force) +{ + if (body != NULL) + { + if (body->shape.type == PHYSICS_POLYGON) + { + PhysicsVertexData vertexData = body->shape.vertexData; + bool collision = false; + + for (unsigned int i = 0; i < vertexData.vertexCount; i++) + { + Vector2 positionA = body->position; + Vector2 positionB = MathMatVector2Product(body->shape.transform, MathVector2Add(body->position, vertexData.positions[i])); + unsigned int nextIndex = (((i + 1) < vertexData.vertexCount) ? (i + 1) : 0); + Vector2 positionC = MathMatVector2Product(body->shape.transform, MathVector2Add(body->position, vertexData.positions[nextIndex])); + + // Check collision between each triangle + float alpha = ((positionB.y - positionC.y)*(position.x - positionC.x) + (positionC.x - positionB.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float beta = ((positionC.y - positionA.y)*(position.x - positionC.x) + (positionA.x - positionC.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0.0f) && (beta > 0.0f) & (gamma > 0.0f)) + { + collision = true; + break; + } + } + + if (collision) + { + int count = vertexData.vertexCount; + Vector2 bodyPos = body->position; + Vector2 *vertices = (Vector2 *)PHYSAC_MALLOC(sizeof(Vector2)*count); + Matrix2x2 trans = body->shape.transform; + for (int i = 0; i < count; i++) vertices[i] = vertexData.positions[i]; + + // Destroy shattered physics body + DestroyPhysicsBody(body); + + for (int i = 0; i < count; i++) + { + int nextIndex = (((i + 1) < count) ? (i + 1) : 0); + Vector2 center = MathTriangleBarycenter(vertices[i], vertices[nextIndex], PHYSAC_VECTOR_ZERO); + center = MathVector2Add(bodyPos, center); + Vector2 offset = MathVector2Subtract(center, bodyPos); + + PhysicsBody body = CreatePhysicsBodyPolygon(center, 10, 3, 10); // Create polygon physics body with relevant values + + PhysicsVertexData vertexData = { 0 }; + vertexData.vertexCount = 3; + + vertexData.positions[0] = MathVector2Subtract(vertices[i], offset); + vertexData.positions[1] = MathVector2Subtract(vertices[nextIndex], offset); + vertexData.positions[2] = MathVector2Subtract(position, center); + + // Separate vertices to avoid unnecessary physics collisions + vertexData.positions[0].x *= 0.95f; + vertexData.positions[0].y *= 0.95f; + vertexData.positions[1].x *= 0.95f; + vertexData.positions[1].y *= 0.95f; + vertexData.positions[2].x *= 0.95f; + vertexData.positions[2].y *= 0.95f; + + // Calculate polygon faces normals + for (unsigned int j = 0; j < vertexData.vertexCount; j++) + { + unsigned int nextVertex = (((j + 1) < vertexData.vertexCount) ? (j + 1) : 0); + Vector2 face = MathVector2Subtract(vertexData.positions[nextVertex], vertexData.positions[j]); + + vertexData.normals[j] = CLITERAL(Vector2){ face.y, -face.x }; + MathVector2Normalize(&vertexData.normals[j]); + } + + // Apply computed vertex data to new physics body shape + body->shape.vertexData = vertexData; + body->shape.transform = trans; + + // Calculate centroid and moment of inertia + center = PHYSAC_VECTOR_ZERO; + float area = 0.0f; + float inertia = 0.0f; + + for (unsigned int j = 0; j < body->shape.vertexData.vertexCount; j++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = body->shape.vertexData.positions[j]; + unsigned int nextVertex = (((j + 1) < body->shape.vertexData.vertexCount) ? (j + 1) : 0); + Vector2 p2 = body->shape.vertexData.positions[nextVertex]; + + float D = MathVector2CrossProduct(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*PHYSAC_K*(p1.x + p2.x); + center.y += triangleArea*PHYSAC_K*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*PHYSAC_K*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + body->mass = area; + body->inverseMass = ((body->mass != 0.0f) ? 1.0f/body->mass : 0.0f); + body->inertia = inertia; + body->inverseInertia = ((body->inertia != 0.0f) ? 1.0f/body->inertia : 0.0f); + + // Calculate explosion force direction + Vector2 pointA = body->position; + Vector2 pointB = MathVector2Subtract(vertexData.positions[1], vertexData.positions[0]); + pointB.x /= 2.0f; + pointB.y /= 2.0f; + Vector2 forceDirection = MathVector2Subtract(MathVector2Add(pointA, MathVector2Add(vertexData.positions[0], pointB)), body->position); + MathVector2Normalize(&forceDirection); + forceDirection.x *= force; + forceDirection.y *= force; + + // Apply force to new physics body + PhysicsAddForce(body, forceDirection); + } + + PHYSAC_FREE(vertices); + } + } + } + else TRACELOG("[PHYSAC] WARNING: PhysicsShatter: NULL physic body\n"); +} + +// Returns the current amount of created physics bodies +PHYSACDEF int GetPhysicsBodiesCount(void) +{ + return physicsBodiesCount; +} + +// Returns a physics body of the bodies pool at a specific index +PHYSACDEF PhysicsBody GetPhysicsBody(int index) +{ + PhysicsBody body = NULL; + + if (index < (int)physicsBodiesCount) + { + body = bodies[index]; + + if (body == NULL) TRACELOG("[PHYSAC] WARNING: GetPhysicsBody: NULL physic body\n"); + } + else TRACELOG("[PHYSAC] WARNING: Physic body index is out of bounds\n"); + + return body; +} + +// Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeType(int index) +{ + int result = -1; + + if (index < (int)physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + + if (body != NULL) result = body->shape.type; + else TRACELOG("[PHYSAC] WARNING: GetPhysicsShapeType: NULL physic body\n"); + } + else TRACELOG("[PHYSAC] WARNING: Physic body index is out of bounds\n"); + + return result; +} + +// Returns the amount of vertices of a physics body shape +PHYSACDEF int GetPhysicsShapeVerticesCount(int index) +{ + int result = 0; + + if (index < (int)physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: result = PHYSAC_DEFAULT_CIRCLE_VERTICES; break; + case PHYSICS_POLYGON: result = body->shape.vertexData.vertexCount; break; + default: break; + } + } + else TRACELOG("[PHYSAC] WARNING: GetPhysicsShapeVerticesCount: NULL physic body\n"); + } + else TRACELOG("[PHYSAC] WARNING: Physic body index is out of bounds\n"); + + return result; +} + +// Returns transformed position of a body shape (body position + vertex transformed position) +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex) +{ + Vector2 position = { 0.0f, 0.0f }; + + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: + { + position.x = body->position.x + cosf(360.0f/PHYSAC_DEFAULT_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + position.y = body->position.y + sinf(360.0f/PHYSAC_DEFAULT_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + } break; + case PHYSICS_POLYGON: + { + PhysicsVertexData vertexData = body->shape.vertexData; + position = MathVector2Add(body->position, MathMatVector2Product(body->shape.transform, vertexData.positions[vertex])); + } break; + default: break; + } + } + else TRACELOG("[PHYSAC] WARNING: GetPhysicsShapeVertex: NULL physic body\n"); + + return position; +} + +// Sets physics body shape transform based on radians parameter +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians) +{ + if (body != NULL) + { + body->orient = radians; + + if (body->shape.type == PHYSICS_POLYGON) body->shape.transform = MathMatFromRadians(radians); + } +} + +// Unitializes and destroys a physics body +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body) +{ + if (body != NULL) + { + int id = body->id; + int index = -1; + + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + if (bodies[i]->id == id) + { + index = i; + break; + } + } + + if (index == -1) + { + TRACELOG("[PHYSAC] WARNING: Requested body (id: %i) can not be found\n", id); + return; // Prevent access to index -1 + } + + // Free body allocated memory + PHYSAC_FREE(body); + usedMemory -= sizeof(PhysicsBodyData); + bodies[index] = NULL; + + // Reorder physics bodies pointers array and its catched index + for (unsigned int i = index; i < physicsBodiesCount; i++) + { + if ((i + 1) < physicsBodiesCount) bodies[i] = bodies[i + 1]; + } + + // Update physics bodies count + physicsBodiesCount--; + + TRACELOG("[PHYSAC] Physic body destroyed successfully (id: %i)\n", id); + } + else TRACELOG("[PHYSAC] WARNING: DestroyPhysicsBody: NULL physic body\n"); +} + +// Destroys created physics bodies and manifolds and resets global values +PHYSACDEF void ResetPhysics(void) +{ + if (physicsBodiesCount > 0) + { + // Unitialize physics bodies dynamic memory allocations + for (int i = physicsBodiesCount - 1; i >= 0; i--) + { + PhysicsBody body = bodies[i]; + + if (body != NULL) + { + PHYSAC_FREE(body); + bodies[i] = NULL; + usedMemory -= sizeof(PhysicsBodyData); + } + } + + physicsBodiesCount = 0; + } + + if (physicsManifoldsCount > 0) + { + // Unitialize physics manifolds dynamic memory allocations + for (int i = physicsManifoldsCount - 1; i >= 0; i--) + { + PhysicsManifold manifold = contacts[i]; + + if (manifold != NULL) + { + PHYSAC_FREE(manifold); + contacts[i] = NULL; + usedMemory -= sizeof(PhysicsManifoldData); + } + } + + physicsManifoldsCount = 0; + } + + TRACELOG("[PHYSAC] Physics module reseted successfully\n"); +} + +// Unitializes physics pointers and exits physics loop thread +PHYSACDEF void ClosePhysics(void) +{ + // Unitialize physics manifolds dynamic memory allocations + if (physicsManifoldsCount > 0) + { + for (unsigned int i = physicsManifoldsCount - 1; i >= 0; i--) + DestroyPhysicsManifold(contacts[i]); + } + + // Unitialize physics bodies dynamic memory allocations + if (physicsBodiesCount > 0) + { + for (unsigned int i = physicsBodiesCount - 1; i >= 0; i--) + DestroyPhysicsBody(bodies[i]); + } + + // Trace log info + if ((physicsBodiesCount > 0) || (usedMemory != 0)) + { + TRACELOG("[PHYSAC] WARNING: Physics module closed with unallocated bodies (BODIES: %i, MEMORY: %i bytes)\n", physicsBodiesCount, usedMemory); + } + else if ((physicsManifoldsCount > 0) || (usedMemory != 0)) + { + TRACELOG("[PHYSAC] WARNING: Pysics module closed with unallocated manifolds (MANIFOLDS: %i, MEMORY: %i bytes)\n", physicsManifoldsCount, usedMemory); + } + else TRACELOG("[PHYSAC] Physics module closed successfully\n"); +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Finds a valid index for a new physics body initialization +static int FindAvailableBodyIndex() +{ + int index = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (unsigned int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == (int)i) + { + index = (int)i; + break; + } + } + + return index; +} + +// Creates a default polygon shape with max vertex distance from polygon pivot +static PhysicsVertexData CreateDefaultPolygon(float radius, int sides) +{ + PhysicsVertexData data = { 0 }; + data.vertexCount = sides; + + // Calculate polygon vertices positions + for (unsigned int i = 0; i < data.vertexCount; i++) + { + data.positions[i].x = (float)cosf(360.0f/sides*i*PHYSAC_DEG2RAD)*radius; + data.positions[i].y = (float)sinf(360.0f/sides*i*PHYSAC_DEG2RAD)*radius; + } + + // Calculate polygon faces normals + for (int i = 0; i < (int)data.vertexCount; i++) + { + int nextIndex = (((i + 1) < sides) ? (i + 1) : 0); + Vector2 face = MathVector2Subtract(data.positions[nextIndex], data.positions[i]); + + data.normals[i] = CLITERAL(Vector2){ face.y, -face.x }; + MathVector2Normalize(&data.normals[i]); + } + + return data; +} + +// Creates a rectangle polygon shape based on a min and max positions +static PhysicsVertexData CreateRectanglePolygon(Vector2 pos, Vector2 size) +{ + PhysicsVertexData data = { 0 }; + data.vertexCount = 4; + + // Calculate polygon vertices positions + data.positions[0] = CLITERAL(Vector2){ pos.x + size.x/2, pos.y - size.y/2 }; + data.positions[1] = CLITERAL(Vector2){ pos.x + size.x/2, pos.y + size.y/2 }; + data.positions[2] = CLITERAL(Vector2){ pos.x - size.x/2, pos.y + size.y/2 }; + data.positions[3] = CLITERAL(Vector2){ pos.x - size.x/2, pos.y - size.y/2 }; + + // Calculate polygon faces normals + for (unsigned int i = 0; i < data.vertexCount; i++) + { + int nextIndex = (((i + 1) < data.vertexCount) ? (i + 1) : 0); + Vector2 face = MathVector2Subtract(data.positions[nextIndex], data.positions[i]); + + data.normals[i] = CLITERAL(Vector2){ face.y, -face.x }; + MathVector2Normalize(&data.normals[i]); + } + + return data; +} + +// Update physics step (dynamics, collisions and position corrections) +void UpdatePhysicsStep(void) +{ + // Clear previous generated collisions information + for (int i = (int)physicsManifoldsCount - 1; i >= 0; i--) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) DestroyPhysicsManifold(manifold); + } + + // Reset physics bodies grounded state + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + body->isGrounded = false; + } + + // Generate new collision information + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody bodyA = bodies[i]; + + if (bodyA != NULL) + { + for (unsigned int j = i + 1; j < physicsBodiesCount; j++) + { + PhysicsBody bodyB = bodies[j]; + + if (bodyB != NULL) + { + if ((bodyA->inverseMass == 0) && (bodyB->inverseMass == 0)) continue; + + PhysicsManifold manifold = CreatePhysicsManifold(bodyA, bodyB); + SolvePhysicsManifold(manifold); + + if (manifold->contactsCount > 0) + { + // Create a new manifold with same information as previously solved manifold and add it to the manifolds pool last slot + PhysicsManifold manifold = CreatePhysicsManifold(bodyA, bodyB); + manifold->penetration = manifold->penetration; + manifold->normal = manifold->normal; + manifold->contacts[0] = manifold->contacts[0]; + manifold->contacts[1] = manifold->contacts[1]; + manifold->contactsCount = manifold->contactsCount; + manifold->restitution = manifold->restitution; + manifold->dynamicFriction = manifold->dynamicFriction; + manifold->staticFriction = manifold->staticFriction; + } + } + } + } + } + + // Integrate forces to physics bodies + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsForces(body); + } + + // Initialize physics manifolds to solve collisions + for (unsigned int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) InitializePhysicsManifolds(manifold); + } + + // Integrate physics collisions impulses to solve collisions + for (unsigned int i = 0; i < PHYSAC_COLLISION_ITERATIONS; i++) + { + for (unsigned int j = 0; j < physicsManifoldsCount; j++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) IntegratePhysicsImpulses(manifold); + } + } + + // Integrate velocity to physics bodies + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsVelocity(body); + } + + // Correct physics bodies positions based on manifolds collision information + for (unsigned int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) CorrectPhysicsPositions(manifold); + } + + // Clear physics bodies forces + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) + { + body->force = PHYSAC_VECTOR_ZERO; + body->torque = 0.0f; + } + } +} + +// Update physics system +// Physics steps are launched at a fixed time step if enabled +PHYSACDEF void UpdatePhysics(void) +{ +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) + static double deltaTimeAccumulator = 0.0; + + // Calculate current time (ms) + currentTime = GetCurrentTime(); + + // Calculate current delta time (ms) + const double delta = currentTime - startTime; + + // Store the time elapsed since the last frame began + deltaTimeAccumulator += delta; + + // Fixed time stepping loop + while (deltaTimeAccumulator >= deltaTime) + { + UpdatePhysicsStep(); + deltaTimeAccumulator -= deltaTime; + } + + // Record the starting of this frame + startTime = currentTime; +#else + UpdatePhysicsStep(); +#endif +} + +PHYSACDEF void SetPhysicsTimeStep(double delta) +{ + deltaTime = delta; +} + +// Finds a valid index for a new manifold initialization +static int FindAvailableManifoldIndex() +{ + int index = -1; + for (int i = 0; i < PHYSAC_MAX_MANIFOLDS; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (unsigned int k = 0; k < physicsManifoldsCount; k++) + { + if (contacts[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + index = i; + break; + } + } + + return index; +} + +// Creates a new physics manifold to solve collision +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b) +{ + PhysicsManifold manifold = (PhysicsManifold)PHYSAC_MALLOC(sizeof(PhysicsManifoldData)); + usedMemory += sizeof(PhysicsManifoldData); + + int id = FindAvailableManifoldIndex(); + if (id != -1) + { + // Initialize new manifold with generic values + manifold->id = id; + manifold->bodyA = a; + manifold->bodyB = b; + manifold->penetration = 0; + manifold->normal = PHYSAC_VECTOR_ZERO; + manifold->contacts[0] = PHYSAC_VECTOR_ZERO; + manifold->contacts[1] = PHYSAC_VECTOR_ZERO; + manifold->contactsCount = 0; + manifold->restitution = 0.0f; + manifold->dynamicFriction = 0.0f; + manifold->staticFriction = 0.0f; + + // Add new body to bodies pointers array and update bodies count + contacts[physicsManifoldsCount] = manifold; + physicsManifoldsCount++; + } + else TRACELOG("[PHYSAC] Physic manifold could not be created, PHYSAC_MAX_MANIFOLDS reached\n"); + + return manifold; +} + +// Unitializes and destroys a physics manifold +static void DestroyPhysicsManifold(PhysicsManifold manifold) +{ + if (manifold != NULL) + { + int id = manifold->id; + int index = -1; + + for (unsigned int i = 0; i < physicsManifoldsCount; i++) + { + if (contacts[i]->id == id) + { + index = i; + break; + } + } + + if (index == -1) return; // Prevent access to index -1 + + // Free manifold allocated memory + PHYSAC_FREE(manifold); + usedMemory -= sizeof(PhysicsManifoldData); + contacts[index] = NULL; + + // Reorder physics manifolds pointers array and its catched index + for (unsigned int i = index; i < physicsManifoldsCount; i++) + { + if ((i + 1) < physicsManifoldsCount) contacts[i] = contacts[i + 1]; + } + + // Update physics manifolds count + physicsManifoldsCount--; + } + else TRACELOG("[PHYSAC] WARNING: DestroyPhysicsManifold: NULL physic manifold\n"); +} + +// Solves a created physics manifold between two physics bodies +static void SolvePhysicsManifold(PhysicsManifold manifold) +{ + switch (manifold->bodyA->shape.type) + { + case PHYSICS_CIRCLE: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolveCircleToCircle(manifold); break; + case PHYSICS_POLYGON: SolveCircleToPolygon(manifold); break; + default: break; + } + } break; + case PHYSICS_POLYGON: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolvePolygonToCircle(manifold); break; + case PHYSICS_POLYGON: SolvePolygonToPolygon(manifold); break; + default: break; + } + } break; + default: break; + } + + // Update physics body grounded state if normal direction is down and grounded state is not set yet in previous manifolds + if (!manifold->bodyB->isGrounded) manifold->bodyB->isGrounded = (manifold->normal.y < 0); +} + +// Solves collision between two circle shape physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + // Calculate translational vector, which is normal + Vector2 normal = MathVector2Subtract(bodyB->position, bodyA->position); + + float distSqr = MathVector2SqrLen(normal); + float radius = bodyA->shape.radius + bodyB->shape.radius; + + // Check if circles are not in contact + if (distSqr >= radius*radius) + { + manifold->contactsCount = 0; + return; + } + + float distance = sqrtf(distSqr); + manifold->contactsCount = 1; + + if (distance == 0.0f) + { + manifold->penetration = bodyA->shape.radius; + manifold->normal = CLITERAL(Vector2){ 1.0f, 0.0f }; + manifold->contacts[0] = bodyA->position; + } + else + { + manifold->penetration = radius - distance; + manifold->normal = CLITERAL(Vector2){ normal.x/distance, normal.y/distance }; // Faster than using MathVector2Normalize() due to sqrt is already performed + manifold->contacts[0] = CLITERAL(Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + } + + // Update physics body grounded state if normal direction is down + if (!bodyA->isGrounded) bodyA->isGrounded = (manifold->normal.y < 0); +} + +// Solves collision between a circle to a polygon shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + manifold->contactsCount = 0; + + // Transform circle center to polygon transform space + Vector2 center = bodyA->position; + center = MathMatVector2Product(MathMatTranspose(bodyB->shape.transform), MathVector2Subtract(center, bodyB->position)); + + // Find edge with minimum penetration + // It is the same concept as using support points in SolvePolygonToPolygon + float separation = -PHYSAC_FLT_MAX; + int faceNormal = 0; + PhysicsVertexData vertexData = bodyB->shape.vertexData; + + for (unsigned int i = 0; i < vertexData.vertexCount; i++) + { + float currentSeparation = MathVector2DotProduct(vertexData.normals[i], MathVector2Subtract(center, vertexData.positions[i])); + + if (currentSeparation > bodyA->shape.radius) return; + + if (currentSeparation > separation) + { + separation = currentSeparation; + faceNormal = i; + } + } + + // Grab face's vertices + Vector2 v1 = vertexData.positions[faceNormal]; + int nextIndex = (((faceNormal + 1) < (int)vertexData.vertexCount) ? (faceNormal + 1) : 0); + Vector2 v2 = vertexData.positions[nextIndex]; + + // Check to see if center is within polygon + if (separation < PHYSAC_EPSILON) + { + manifold->contactsCount = 1; + Vector2 normal = MathMatVector2Product(bodyB->shape.transform, vertexData.normals[faceNormal]); + manifold->normal = CLITERAL(Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = CLITERAL(Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->penetration = bodyA->shape.radius; + return; + } + + // Determine which voronoi region of the edge center of circle lies within + float dot1 = MathVector2DotProduct(MathVector2Subtract(center, v1), MathVector2Subtract(v2, v1)); + float dot2 = MathVector2DotProduct(MathVector2Subtract(center, v2), MathVector2Subtract(v1, v2)); + manifold->penetration = bodyA->shape.radius - separation; + + if (dot1 <= 0.0f) // Closest to v1 + { + if (MathVector2SqrDistance(center, v1) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = MathVector2Subtract(v1, center); + normal = MathMatVector2Product(bodyB->shape.transform, normal); + MathVector2Normalize(&normal); + manifold->normal = normal; + v1 = MathMatVector2Product(bodyB->shape.transform, v1); + v1 = MathVector2Add(v1, bodyB->position); + manifold->contacts[0] = v1; + } + else if (dot2 <= 0.0f) // Closest to v2 + { + if (MathVector2SqrDistance(center, v2) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = MathVector2Subtract(v2, center); + v2 = MathMatVector2Product(bodyB->shape.transform, v2); + v2 = MathVector2Add(v2, bodyB->position); + manifold->contacts[0] = v2; + normal = MathMatVector2Product(bodyB->shape.transform, normal); + MathVector2Normalize(&normal); + manifold->normal = normal; + } + else // Closest to face + { + Vector2 normal = vertexData.normals[faceNormal]; + + if (MathVector2DotProduct(MathVector2Subtract(center, v1), normal) > bodyA->shape.radius) return; + + normal = MathMatVector2Product(bodyB->shape.transform, normal); + manifold->normal = CLITERAL(Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = CLITERAL(Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->contactsCount = 1; + } +} + +// Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + manifold->bodyA = bodyB; + manifold->bodyB = bodyA; + SolveCircleToPolygon(manifold); + + manifold->normal.x *= -1.0f; + manifold->normal.y *= -1.0f; +} + +// Solves collision between two polygons shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold) +{ + if ((manifold->bodyA == NULL) || (manifold->bodyB == NULL)) return; + + PhysicsShape bodyA = manifold->bodyA->shape; + PhysicsShape bodyB = manifold->bodyB->shape; + manifold->contactsCount = 0; + + // Check for separating axis with A shape's face planes + int faceA = 0; + float penetrationA = FindAxisLeastPenetration(&faceA, bodyA, bodyB); + if (penetrationA >= 0.0f) return; + + // Check for separating axis with B shape's face planes + int faceB = 0; + float penetrationB = FindAxisLeastPenetration(&faceB, bodyB, bodyA); + if (penetrationB >= 0.0f) return; + + int referenceIndex = 0; + bool flip = false; // Always point from A shape to B shape + + PhysicsShape refPoly; // Reference + PhysicsShape incPoly; // Incident + + // Determine which shape contains reference face + // Checking bias range for penetration + if (penetrationA >= (penetrationB*0.95f + penetrationA*0.01f)) + { + refPoly = bodyA; + incPoly = bodyB; + referenceIndex = faceA; + } + else + { + refPoly = bodyB; + incPoly = bodyA; + referenceIndex = faceB; + flip = true; + } + + // World space incident face + Vector2 incidentFace[2]; + FindIncidentFace(&incidentFace[0], &incidentFace[1], refPoly, incPoly, referenceIndex); + + // Setup reference face vertices + PhysicsVertexData refData = refPoly.vertexData; + Vector2 v1 = refData.positions[referenceIndex]; + referenceIndex = (((referenceIndex + 1) < (int)refData.vertexCount) ? (referenceIndex + 1) : 0); + Vector2 v2 = refData.positions[referenceIndex]; + + // Transform vertices to world space + v1 = MathMatVector2Product(refPoly.transform, v1); + v1 = MathVector2Add(v1, refPoly.body->position); + v2 = MathMatVector2Product(refPoly.transform, v2); + v2 = MathVector2Add(v2, refPoly.body->position); + + // Calculate reference face side normal in world space + Vector2 sidePlaneNormal = MathVector2Subtract(v2, v1); + MathVector2Normalize(&sidePlaneNormal); + + // Orthogonalize + Vector2 refFaceNormal = { sidePlaneNormal.y, -sidePlaneNormal.x }; + float refC = MathVector2DotProduct(refFaceNormal, v1); + float negSide = MathVector2DotProduct(sidePlaneNormal, v1)*-1; + float posSide = MathVector2DotProduct(sidePlaneNormal, v2); + + // MathVector2Clip incident face to reference face side planes (due to floating point error, possible to not have required points + if (MathVector2Clip(CLITERAL(Vector2){ -sidePlaneNormal.x, -sidePlaneNormal.y }, &incidentFace[0], &incidentFace[1], negSide) < 2) return; + if (MathVector2Clip(sidePlaneNormal, &incidentFace[0], &incidentFace[1], posSide) < 2) return; + + // Flip normal if required + manifold->normal = (flip ? CLITERAL(Vector2){ -refFaceNormal.x, -refFaceNormal.y } : refFaceNormal); + + // Keep points behind reference face + int currentPoint = 0; // MathVector2Clipped points behind reference face + float separation = MathVector2DotProduct(refFaceNormal, incidentFace[0]) - refC; + if (separation <= 0.0f) + { + manifold->contacts[currentPoint] = incidentFace[0]; + manifold->penetration = -separation; + currentPoint++; + } + else manifold->penetration = 0.0f; + + separation = MathVector2DotProduct(refFaceNormal, incidentFace[1]) - refC; + + if (separation <= 0.0f) + { + manifold->contacts[currentPoint] = incidentFace[1]; + manifold->penetration += -separation; + currentPoint++; + + // Calculate total penetration average + manifold->penetration /= currentPoint; + } + + manifold->contactsCount = currentPoint; +} + +// Integrates physics forces into velocity +static void IntegratePhysicsForces(PhysicsBody body) +{ + if ((body == NULL) || (body->inverseMass == 0.0f) || !body->enabled) return; + + body->velocity.x += (float)((body->force.x*body->inverseMass)*(deltaTime/2.0)); + body->velocity.y += (float)((body->force.y*body->inverseMass)*(deltaTime/2.0)); + + if (body->useGravity) + { + body->velocity.x += (float)(gravityForce.x*(deltaTime/1000/2.0)); + body->velocity.y += (float)(gravityForce.y*(deltaTime/1000/2.0)); + } + + if (!body->freezeOrient) body->angularVelocity += (float)(body->torque*body->inverseInertia*(deltaTime/2.0)); +} + +// Initializes physics manifolds to solve collisions +static void InitializePhysicsManifolds(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + // Calculate average restitution, static and dynamic friction + manifold->restitution = sqrtf(bodyA->restitution*bodyB->restitution); + manifold->staticFriction = sqrtf(bodyA->staticFriction*bodyB->staticFriction); + manifold->dynamicFriction = sqrtf(bodyA->dynamicFriction*bodyB->dynamicFriction); + + for (unsigned int i = 0; i < manifold->contactsCount; i++) + { + // Caculate radius from center of mass to contact + Vector2 radiusA = MathVector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = MathVector2Subtract(manifold->contacts[i], bodyB->position); + + Vector2 crossA = MathVector2Product(radiusA, bodyA->angularVelocity); + Vector2 crossB = MathVector2Product(radiusB, bodyB->angularVelocity); + + Vector2 radiusV = { 0.0f, 0.0f }; + radiusV.x = bodyB->velocity.x + crossB.x - bodyA->velocity.x - crossA.x; + radiusV.y = bodyB->velocity.y + crossB.y - bodyA->velocity.y - crossA.y; + + // Determine if we should perform a resting collision or not; + // The idea is if the only thing moving this object is gravity, then the collision should be performed without any restitution + if (MathVector2SqrLen(radiusV) < (MathVector2SqrLen(CLITERAL(Vector2){ (float)(gravityForce.x*deltaTime/1000), (float)(gravityForce.y*deltaTime/1000) }) + PHYSAC_EPSILON)) manifold->restitution = 0; + } +} + +// Integrates physics collisions impulses to solve collisions +static void IntegratePhysicsImpulses(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + // Early out and positional correct if both objects have infinite mass + if (fabs(bodyA->inverseMass + bodyB->inverseMass) <= PHYSAC_EPSILON) + { + bodyA->velocity = PHYSAC_VECTOR_ZERO; + bodyB->velocity = PHYSAC_VECTOR_ZERO; + return; + } + + for (unsigned int i = 0; i < manifold->contactsCount; i++) + { + // Calculate radius from center of mass to contact + Vector2 radiusA = MathVector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = MathVector2Subtract(manifold->contacts[i], bodyB->position); + + // Calculate relative velocity + Vector2 radiusV = { 0.0f, 0.0f }; + radiusV.x = bodyB->velocity.x + MathVector2Product(radiusB, bodyB->angularVelocity).x - bodyA->velocity.x - MathVector2Product(radiusA, bodyA->angularVelocity).x; + radiusV.y = bodyB->velocity.y + MathVector2Product(radiusB, bodyB->angularVelocity).y - bodyA->velocity.y - MathVector2Product(radiusA, bodyA->angularVelocity).y; + + // Relative velocity along the normal + float contactVelocity = MathVector2DotProduct(radiusV, manifold->normal); + + // Do not resolve if velocities are separating + if (contactVelocity > 0.0f) return; + + float raCrossN = MathVector2CrossProduct(radiusA, manifold->normal); + float rbCrossN = MathVector2CrossProduct(radiusB, manifold->normal); + + float inverseMassSum = bodyA->inverseMass + bodyB->inverseMass + (raCrossN*raCrossN)*bodyA->inverseInertia + (rbCrossN*rbCrossN)*bodyB->inverseInertia; + + // Calculate impulse scalar value + float impulse = -(1.0f + manifold->restitution)*contactVelocity; + impulse /= inverseMassSum; + impulse /= (float)manifold->contactsCount; + + // Apply impulse to each physics body + Vector2 impulseV = { manifold->normal.x*impulse, manifold->normal.y*impulse }; + + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-impulseV.x); + bodyA->velocity.y += bodyA->inverseMass*(-impulseV.y); + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathVector2CrossProduct(radiusA, CLITERAL(Vector2){ -impulseV.x, -impulseV.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(impulseV.x); + bodyB->velocity.y += bodyB->inverseMass*(impulseV.y); + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathVector2CrossProduct(radiusB, impulseV); + } + + // Apply friction impulse to each physics body + radiusV.x = bodyB->velocity.x + MathVector2Product(radiusB, bodyB->angularVelocity).x - bodyA->velocity.x - MathVector2Product(radiusA, bodyA->angularVelocity).x; + radiusV.y = bodyB->velocity.y + MathVector2Product(radiusB, bodyB->angularVelocity).y - bodyA->velocity.y - MathVector2Product(radiusA, bodyA->angularVelocity).y; + + Vector2 tangent = { radiusV.x - (manifold->normal.x*MathVector2DotProduct(radiusV, manifold->normal)), radiusV.y - (manifold->normal.y*MathVector2DotProduct(radiusV, manifold->normal)) }; + MathVector2Normalize(&tangent); + + // Calculate impulse tangent magnitude + float impulseTangent = -MathVector2DotProduct(radiusV, tangent); + impulseTangent /= inverseMassSum; + impulseTangent /= (float)manifold->contactsCount; + + float absImpulseTangent = (float)fabs(impulseTangent); + + // Don't apply tiny friction impulses + if (absImpulseTangent <= PHYSAC_EPSILON) return; + + // Apply coulumb's law + Vector2 tangentImpulse = { 0.0f, 0.0f }; + if (absImpulseTangent < impulse*manifold->staticFriction) tangentImpulse = CLITERAL(Vector2){ tangent.x*impulseTangent, tangent.y*impulseTangent }; + else tangentImpulse = CLITERAL(Vector2){ tangent.x*-impulse*manifold->dynamicFriction, tangent.y*-impulse*manifold->dynamicFriction }; + + // Apply friction impulse + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-tangentImpulse.x); + bodyA->velocity.y += bodyA->inverseMass*(-tangentImpulse.y); + + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathVector2CrossProduct(radiusA, CLITERAL(Vector2){ -tangentImpulse.x, -tangentImpulse.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(tangentImpulse.x); + bodyB->velocity.y += bodyB->inverseMass*(tangentImpulse.y); + + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathVector2CrossProduct(radiusB, tangentImpulse); + } + } +} + +// Integrates physics velocity into position and forces +static void IntegratePhysicsVelocity(PhysicsBody body) +{ + if ((body == NULL) ||!body->enabled) return; + + body->position.x += (float)(body->velocity.x*deltaTime); + body->position.y += (float)(body->velocity.y*deltaTime); + + if (!body->freezeOrient) body->orient += (float)(body->angularVelocity*deltaTime); + body->shape.transform = MathMatFromRadians(body->orient); + + IntegratePhysicsForces(body); +} + +// Corrects physics bodies positions based on manifolds collision information +static void CorrectPhysicsPositions(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + Vector2 correction = { 0.0f, 0.0f }; + correction.x = (PHYSAC_MAX(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0.0f)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.x*PHYSAC_PENETRATION_CORRECTION; + correction.y = (PHYSAC_MAX(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0.0f)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.y*PHYSAC_PENETRATION_CORRECTION; + + if (bodyA->enabled) + { + bodyA->position.x -= correction.x*bodyA->inverseMass; + bodyA->position.y -= correction.y*bodyA->inverseMass; + } + + if (bodyB->enabled) + { + bodyB->position.x += correction.x*bodyB->inverseMass; + bodyB->position.y += correction.y*bodyB->inverseMass; + } +} + +// Returns the extreme point along a direction within a polygon +static Vector2 GetSupport(PhysicsShape shape, Vector2 dir) +{ + float bestProjection = -PHYSAC_FLT_MAX; + Vector2 bestVertex = { 0.0f, 0.0f }; + PhysicsVertexData data = shape.vertexData; + + for (unsigned int i = 0; i < data.vertexCount; i++) + { + Vector2 vertex = data.positions[i]; + float projection = MathVector2DotProduct(vertex, dir); + + if (projection > bestProjection) + { + bestVertex = vertex; + bestProjection = projection; + } + } + + return bestVertex; +} + +// Finds polygon shapes axis least penetration +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB) +{ + float bestDistance = -PHYSAC_FLT_MAX; + int bestIndex = 0; + + PhysicsVertexData dataA = shapeA.vertexData; + //PhysicsVertexData dataB = shapeB.vertexData; + + for (unsigned int i = 0; i < dataA.vertexCount; i++) + { + // Retrieve a face normal from A shape + Vector2 normal = dataA.normals[i]; + Vector2 transNormal = MathMatVector2Product(shapeA.transform, normal); + + // Transform face normal into B shape's model space + Matrix2x2 buT = MathMatTranspose(shapeB.transform); + normal = MathMatVector2Product(buT, transNormal); + + // Retrieve support point from B shape along -n + Vector2 support = GetSupport(shapeB, CLITERAL(Vector2){ -normal.x, -normal.y }); + + // Retrieve vertex on face from A shape, transform into B shape's model space + Vector2 vertex = dataA.positions[i]; + vertex = MathMatVector2Product(shapeA.transform, vertex); + vertex = MathVector2Add(vertex, shapeA.body->position); + vertex = MathVector2Subtract(vertex, shapeB.body->position); + vertex = MathMatVector2Product(buT, vertex); + + // Compute penetration distance in B shape's model space + float distance = MathVector2DotProduct(normal, MathVector2Subtract(support, vertex)); + + // Store greatest distance + if (distance > bestDistance) + { + bestDistance = distance; + bestIndex = i; + } + } + + *faceIndex = bestIndex; + return bestDistance; +} + +// Finds two polygon shapes incident face +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index) +{ + PhysicsVertexData refData = ref.vertexData; + PhysicsVertexData incData = inc.vertexData; + + Vector2 referenceNormal = refData.normals[index]; + + // Calculate normal in incident's frame of reference + referenceNormal = MathMatVector2Product(ref.transform, referenceNormal); // To world space + referenceNormal = MathMatVector2Product(MathMatTranspose(inc.transform), referenceNormal); // To incident's model space + + // Find most anti-normal face on polygon + int incidentFace = 0; + float minDot = PHYSAC_FLT_MAX; + + for (unsigned int i = 0; i < incData.vertexCount; i++) + { + float dot = MathVector2DotProduct(referenceNormal, incData.normals[i]); + + if (dot < minDot) + { + minDot = dot; + incidentFace = i; + } + } + + // Assign face vertices for incident face + *v0 = MathMatVector2Product(inc.transform, incData.positions[incidentFace]); + *v0 = MathVector2Add(*v0, inc.body->position); + incidentFace = (((incidentFace + 1) < (int)incData.vertexCount) ? (incidentFace + 1) : 0); + *v1 = MathMatVector2Product(inc.transform, incData.positions[incidentFace]); + *v1 = MathVector2Add(*v1, inc.body->position); +} + +// Returns clipping value based on a normal and two faces +static int MathVector2Clip(Vector2 normal, Vector2 *faceA, Vector2 *faceB, float clip) +{ + int sp = 0; + Vector2 out[2] = { *faceA, *faceB }; + + // Retrieve distances from each endpoint to the line + float distanceA = MathVector2DotProduct(normal, *faceA) - clip; + float distanceB = MathVector2DotProduct(normal, *faceB) - clip; + + // If negative (behind plane) + if (distanceA <= 0.0f) out[sp++] = *faceA; + if (distanceB <= 0.0f) out[sp++] = *faceB; + + // If the points are on different sides of the plane + if ((distanceA*distanceB) < 0.0f) + { + // Push intersection point + float alpha = distanceA/(distanceA - distanceB); + out[sp] = *faceA; + Vector2 delta = MathVector2Subtract(*faceB, *faceA); + delta.x *= alpha; + delta.y *= alpha; + out[sp] = MathVector2Add(out[sp], delta); + sp++; + } + + // Assign the new converted values + *faceA = out[0]; + *faceB = out[1]; + + return sp; +} + +// Returns the barycenter of a triangle given by 3 points +static Vector2 MathTriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3) +{ + Vector2 result = { 0.0f, 0.0f }; + + result.x = (v1.x + v2.x + v3.x)/3; + result.y = (v1.y + v2.y + v3.y)/3; + + return result; +} + +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) +// Initializes hi-resolution MONOTONIC timer +static void InitTimer(void) +{ +#if defined(_WIN32) + QueryPerformanceFrequency((unsigned long long int *) &frequency); +#endif + +#if defined(__EMSCRIPTEN__) || defined(__linux__) + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) frequency = 1000000000; +#endif + +#if defined(__APPLE__) + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + frequency = (timebase.denom*1e9)/timebase.numer; +#endif + + baseClockTicks = (double)GetClockTicks(); // Get MONOTONIC clock time offset + startTime = GetCurrentTime(); // Get current time in milliseconds +} + +// Get hi-res MONOTONIC time measure in clock ticks +static unsigned long long int GetClockTicks(void) +{ + unsigned long long int value = 0; + +#if defined(_WIN32) + QueryPerformanceCounter((unsigned long long int *) &value); +#endif + +#if defined(__linux__) + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + value = (unsigned long long int)now.tv_sec*(unsigned long long int)1000000000 + (unsigned long long int)now.tv_nsec; +#endif + +#if defined(__APPLE__) + value = mach_absolute_time(); +#endif + + return value; +} + +// Get current time in milliseconds +static double GetCurrentTime(void) +{ + return (double)(GetClockTicks() - baseClockTicks)/frequency*1000; +} +#endif // !PHYSAC_AVOID_TIMMING_SYSTEM + + +// Returns the cross product of a vector and a value +static inline Vector2 MathVector2Product(Vector2 vector, float value) +{ + Vector2 result = { -value*vector.y, value*vector.x }; + return result; +} + +// Returns the cross product of two vectors +static inline float MathVector2CrossProduct(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.y - v1.y*v2.x); +} + +// Returns the len square root of a vector +static inline float MathVector2SqrLen(Vector2 vector) +{ + return (vector.x*vector.x + vector.y*vector.y); +} + +// Returns the dot product of two vectors +static inline float MathVector2DotProduct(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.x + v1.y*v2.y); +} + +// Returns the square root of distance between two vectors +static inline float MathVector2SqrDistance(Vector2 v1, Vector2 v2) +{ + Vector2 dir = MathVector2Subtract(v1, v2); + return MathVector2DotProduct(dir, dir); +} + +// Returns the normalized values of a vector +static void MathVector2Normalize(Vector2 *vector) +{ + float length, ilength; + + Vector2 aux = *vector; + length = sqrtf(aux.x*aux.x + aux.y*aux.y); + + if (length == 0) length = 1.0f; + + ilength = 1.0f/length; + + vector->x *= ilength; + vector->y *= ilength; +} + +// Returns the sum of two given vectors +static inline Vector2 MathVector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + return result; +} + +// Returns the subtract of two given vectors +static inline Vector2 MathVector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + return result; +} + +// Creates a matrix 2x2 from a given radians value +static Matrix2x2 MathMatFromRadians(float radians) +{ + float cos = cosf(radians); + float sin = sinf(radians); + + Matrix2x2 result = { cos, -sin, sin, cos }; + return result; +} + +// Returns the transpose of a given matrix 2x2 +static inline Matrix2x2 MathMatTranspose(Matrix2x2 matrix) +{ + Matrix2x2 result = { matrix.m00, matrix.m10, matrix.m01, matrix.m11 }; + return result; +} + +// Multiplies a vector by a matrix 2x2 +static inline Vector2 MathMatVector2Product(Matrix2x2 matrix, Vector2 vector) +{ + Vector2 result = { matrix.m00*vector.x + matrix.m01*vector.y, matrix.m10*vector.x + matrix.m11*vector.y }; + return result; +} + +#endif // PHYSAC_IMPLEMENTATION diff --git a/raylib/raudio.c b/raylib/raudio.c new file mode 100644 index 0000000..2b6b0a1 --- /dev/null +++ b/raylib/raudio.c @@ -0,0 +1,2414 @@ +/********************************************************************************************** +* +* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio +* +* FEATURES: +* - Manage audio device (init/close) +* - Manage raw audio context +* - Manage mixing channels +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* +* CONFIGURATION: +* +* #define RAUDIO_STANDALONE +* Define to use the module as standalone library (independently of raylib). +* Required types and functions are defined in the same module. +* +* #define SUPPORT_FILEFORMAT_WAV +* #define SUPPORT_FILEFORMAT_OGG +* #define SUPPORT_FILEFORMAT_XM +* #define SUPPORT_FILEFORMAT_MOD +* #define SUPPORT_FILEFORMAT_FLAC +* #define SUPPORT_FILEFORMAT_MP3 +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* DEPENDENCIES: +* miniaudio.h - Audio device management lib (https://github.com/dr-soft/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to miniaudio library +* +* Joshua Reisenauer (github: @kd7tck) (2015) +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if defined(RAUDIO_STANDALONE) + #include "raudio.h" + #include // Required for: va_list, va_start(), vfprintf(), va_end() +#else + #include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include "utils.h" // Required for: fopen() Android mapping +#endif + +#if defined(_WIN32) +// To avoid conflicting windows.h symbols with raylib, some flags are defined +// WARNING: Those flags avoid inclusion of some Win32 headers that could be required +// by user at some point and won't be included... +//------------------------------------------------------------------------------------- + +// If defined, the following flags inhibit definition of the indicated items. +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI defines and routines +#define NOKERNEL // All KERNEL defines and routines +#define NOUSER // All USER defines and routines +//#define NONLS // All NLS defines and routines +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff. +#define NOHELP // Help engine interface. +#define NOPROFILER // Profiler interface. +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions + +// Type required before windows.h inclusion +typedef struct tagMSG *LPMSG; + +#include + +// Type required by some unused function... +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER, *PBITMAPINFOHEADER; + +#include +#include +#include + +// Some required types defined for MSVC/TinyC compiler +#if defined(_MSC_VER) || defined(__TINYC__) + #include "propidl.h" +#endif +#endif + +#define MA_MALLOC RL_MALLOC +#define MA_FREE RL_FREE + +#define MA_NO_JACK +#define MA_NO_WAV +#define MA_NO_FLAC +#define MA_NO_MP3 +#define MINIAUDIO_IMPLEMENTATION +//#define MA_DEBUG_OUTPUT +#include "external/miniaudio.h" // miniaudio library +#undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro + +#include // Required for: malloc(), free() +#include // Required for: FILE, fopen(), fclose(), fread() + +#if defined(RAUDIO_STANDALONE) + #include // Required for: strcmp() [Used in IsFileExtension()] + + #if !defined(TRACELOG) + #define TRACELOG(level, ...) (void)0 + #endif + + // Allow custom memory allocators + #ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) + #endif + #ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) + #endif + #ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) + #endif + #ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) + #endif +#endif + +#if defined(SUPPORT_FILEFORMAT_OGG) + // TODO: Remap malloc()/free() calls to RL_MALLOC/RL_FREE + + #define STB_VORBIS_IMPLEMENTATION + #include "external/stb_vorbis.h" // OGG loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_XM) + #define JARXM_MALLOC RL_MALLOC + #define JARXM_FREE RL_FREE + + #define JAR_XM_IMPLEMENTATION + #include "external/jar_xm.h" // XM loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_MOD) + #define JARMOD_MALLOC RL_MALLOC + #define JARMOD_FREE RL_FREE + + #define JAR_MOD_IMPLEMENTATION + #include "external/jar_mod.h" // MOD loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_WAV) + #define DRWAV_MALLOC RL_MALLOC + #define DRWAV_REALLOC RL_REALLOC + #define DRWAV_FREE RL_FREE + + #define DR_WAV_IMPLEMENTATION + #include "external/dr_wav.h" // WAV loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_MP3) + #define DRMP3_MALLOC RL_MALLOC + #define DRMP3_REALLOC RL_REALLOC + #define DRMP3_FREE RL_FREE + + #define DR_MP3_IMPLEMENTATION + #include "external/dr_mp3.h" // MP3 loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_FLAC) + #define DRFLAC_MALLOC RL_MALLOC + #define DRFLAC_REALLOC RL_REALLOC + #define DRFLAC_FREE RL_FREE + + #define DR_FLAC_IMPLEMENTATION + #define DR_FLAC_NO_WIN32_IO + #include "external/dr_flac.h" // FLAC loading functions +#endif + +#if defined(_MSC_VER) + #undef bool +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef AUDIO_DEVICE_FORMAT + #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (float-32bit) +#endif +#ifndef AUDIO_DEVICE_CHANNELS + #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo +#endif + +#ifndef AUDIO_DEVICE_SAMPLE_RATE + #define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output channels: stereo +#endif +#ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS + #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels +#endif +#ifndef DEFAULT_AUDIO_BUFFER_SIZE + #define DEFAULT_AUDIO_BUFFER_SIZE 4096 // Default audio buffer size +#endif + + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Music context type +// NOTE: Depends on data structure provided by the library +// in charge of reading the different file types +typedef enum { + MUSIC_AUDIO_NONE = 0, + MUSIC_AUDIO_WAV, + MUSIC_AUDIO_OGG, + MUSIC_AUDIO_FLAC, + MUSIC_AUDIO_MP3, + MUSIC_MODULE_XM, + MUSIC_MODULE_MOD +} MusicContextType; + +#if defined(RAUDIO_STANDALONE) +typedef enum { + LOG_ALL, + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE +} TraceLogLevel; +#endif + +// NOTE: Different logic is used when feeding data to the playback device +// depending on whether or not data is streamed (Music vs Sound) +typedef enum { + AUDIO_BUFFER_USAGE_STATIC = 0, + AUDIO_BUFFER_USAGE_STREAM +} AudioBufferUsage; + +// Audio buffer structure +struct rAudioBuffer { + ma_data_converter converter; // Audio data converter + + float volume; // Audio buffer volume + float pitch; // Audio buffer pitch + + bool playing; // Audio buffer state: AUDIO_PLAYING + bool paused; // Audio buffer state: AUDIO_PAUSED + bool looping; // Audio buffer looping, always true for AudioStreams + int usage; // Audio buffer usage mode: STATIC or STREAM + + bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer) + unsigned int sizeInFrames; // Total buffer size in frames + unsigned int frameCursorPos; // Frame cursor position + unsigned int totalFramesProcessed; // Total frames processed in this buffer (required for play timing) + + unsigned char *data; // Data buffer, on music stream keeps filling + + rAudioBuffer *next; // Next audio buffer on the list + rAudioBuffer *prev; // Previous audio buffer on the list +}; + +#define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision + +// Audio data context +typedef struct AudioData { + struct { + ma_context context; // miniaudio context data + ma_device device; // miniaudio device + ma_mutex lock; // miniaudio mutex lock + bool isReady; // Check if audio device is ready + } System; + struct { + AudioBuffer *first; // Pointer to first AudioBuffer in the list + AudioBuffer *last; // Pointer to last AudioBuffer in the list + int defaultSize; // Default audio buffer size for audio streams + } Buffer; + struct { + unsigned int poolCounter; // AudioBuffer pointers pool counter + AudioBuffer *pool[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // Multichannel AudioBuffer pointers pool + unsigned int channels[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // AudioBuffer pool channels + } MultiChannel; +} AudioData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static AudioData AUDIO = { // Global AUDIO context + + // NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number + // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a + // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough + // In case of music-stalls, just increase this number + .Buffer.defaultSize = 0 +}; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message); +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume); + +#if defined(SUPPORT_FILEFORMAT_WAV) +static Wave LoadWAV(const unsigned char *fileData, unsigned int fileSize); // Load WAV file +static int SaveWAV(Wave wave, const char *fileName); // Save wave data as WAV file +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) +static Wave LoadOGG(const unsigned char *fileData, unsigned int fileSize); // Load OGG file +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) +static Wave LoadFLAC(const unsigned char *fileData, unsigned int fileSize); // Load FLAC file +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) +static Wave LoadMP3(const unsigned char *fileData, unsigned int fileSize); // Load MP3 file +#endif + +#if defined(RAUDIO_STANDALONE) +static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension +static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) +static bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write) +static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated +#endif + +//---------------------------------------------------------------------------------- +// AudioBuffer management functions declaration +// NOTE: Those functions are not exposed by raylib... for the moment +//---------------------------------------------------------------------------------- +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage); +void UnloadAudioBuffer(AudioBuffer *buffer); + +bool IsAudioBufferPlaying(AudioBuffer *buffer); +void PlayAudioBuffer(AudioBuffer *buffer); +void StopAudioBuffer(AudioBuffer *buffer); +void PauseAudioBuffer(AudioBuffer *buffer); +void ResumeAudioBuffer(AudioBuffer *buffer); +void SetAudioBufferVolume(AudioBuffer *buffer, float volume); +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); +void TrackAudioBuffer(AudioBuffer *buffer); +void UntrackAudioBuffer(AudioBuffer *buffer); +int GetAudioStreamBufferSizeDefault(); + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Device initialization and Closing +//---------------------------------------------------------------------------------- +// Initialize audio device +void InitAudioDevice(void) +{ + // TODO: Load AUDIO context memory dynamically? + + // Init audio context + ma_context_config ctxConfig = ma_context_config_init(); + ctxConfig.logCallback = OnLog; + + ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context"); + return; + } + + // Init audio device + // NOTE: Using the default device. Format is floating point because it simplifies mixing. + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device. + config.playback.format = AUDIO_DEVICE_FORMAT; + config.playback.channels = AUDIO_DEVICE_CHANNELS; + config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.System.device. + config.capture.format = ma_format_s16; + config.capture.channels = 1; + config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE; + config.dataCallback = OnSendAudioDataToDevice; + config.pUserData = NULL; + + result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device"); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running + // while there's at least one sound being played. + result = ma_device_start(&AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Mixing happens on a seperate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may + // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. + if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Init dummy audio buffers pool for multichannel sound playing + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + // WARNING: An empty audioBuffer is created (data = 0) + // AudioBuffer data just points to loaded sound data + AUDIO.MultiChannel.pool[i] = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC); + } + + TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Backend: miniaudio / %s", ma_get_backend_name(AUDIO.System.context.backend)); + TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat)); + TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels); + TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate); + TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods); + + AUDIO.System.isReady = true; +} + +// Close the audio device for all contexts +void CloseAudioDevice(void) +{ + if (AUDIO.System.isReady) + { + // Unload dummy audio buffers pool + // WARNING: They can be pointing to already unloaded data + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + //UnloadAudioBuffer(AUDIO.MultiChannel.pool[i]); + if (AUDIO.MultiChannel.pool[i] != NULL) + { + ma_data_converter_uninit(&AUDIO.MultiChannel.pool[i]->converter); + UntrackAudioBuffer(AUDIO.MultiChannel.pool[i]); + //RL_FREE(buffer->data); // Already unloaded by UnloadSound() + RL_FREE(AUDIO.MultiChannel.pool[i]); + } + } + + ma_mutex_uninit(&AUDIO.System.lock); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + + AUDIO.System.isReady = false; + + TRACELOG(LOG_INFO, "AUDIO: Device closed successfully"); + } + else TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized"); +} + +// Check if device has been initialized successfully +bool IsAudioDeviceReady(void) +{ + return AUDIO.System.isReady; +} + +// Set master volume (listener) +void SetMasterVolume(float volume) +{ + ma_device_set_master_volume(&AUDIO.System.device, volume); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Buffer management +//---------------------------------------------------------------------------------- + +// Initialize a new audio buffer (filled with silence) +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer)); + + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer"); + return NULL; + } + + if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1); + + // Audio data runs through a format converter + ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate); + converterConfig.resampling.allowDynamicSampleRate = true; // Required for pitch shifting + + ma_result result = ma_data_converter_init(&converterConfig, &audioBuffer->converter); + + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline"); + RL_FREE(audioBuffer); + return NULL; + } + + // Init audio buffer values + audioBuffer->volume = 1.0f; + audioBuffer->pitch = 1.0f; + audioBuffer->playing = false; + audioBuffer->paused = false; + audioBuffer->looping = false; + audioBuffer->usage = usage; + audioBuffer->frameCursorPos = 0; + audioBuffer->sizeInFrames = sizeInFrames; + + // Buffers should be marked as processed by default so that a call to + // UpdateAudioStream() immediately after initialization works correctly + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; + + // Track audio buffer to linked list next position + TrackAudioBuffer(audioBuffer); + + return audioBuffer; +} + +// Delete an audio buffer +void UnloadAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + ma_data_converter_uninit(&buffer->converter); + UntrackAudioBuffer(buffer); + RL_FREE(buffer->data); + RL_FREE(buffer); + } +} + +// Check if an audio buffer is playing +bool IsAudioBufferPlaying(AudioBuffer *buffer) +{ + bool result = false; + + if (buffer != NULL) result = (buffer->playing && !buffer->paused); + + return result; +} + +// Play an audio buffer +// NOTE: Buffer is restarted to the start. +// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained. +void PlayAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + buffer->playing = true; + buffer->paused = false; + buffer->frameCursorPos = 0; + } +} + +// Stop an audio buffer +void StopAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + if (IsAudioBufferPlaying(buffer)) + { + buffer->playing = false; + buffer->paused = false; + buffer->frameCursorPos = 0; + buffer->totalFramesProcessed = 0; + buffer->isSubBufferProcessed[0] = true; + buffer->isSubBufferProcessed[1] = true; + } + } +} + +// Pause an audio buffer +void PauseAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) buffer->paused = true; +} + +// Resume an audio buffer +void ResumeAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) buffer->paused = false; +} + +// Set volume for an audio buffer +void SetAudioBufferVolume(AudioBuffer *buffer, float volume) +{ + if (buffer != NULL) buffer->volume = volume; +} + +// Set pitch for an audio buffer +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) +{ + if ((buffer != NULL) && (pitch > 0.0f)) + { + // Pitching is just an adjustment of the sample rate. + // Note that this changes the duration of the sound: + // - higher pitches will make the sound faster + // - lower pitches make it slower + ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.config.sampleRateOut/pitch); + ma_data_converter_set_rate(&buffer->converter, buffer->converter.config.sampleRateIn, outputSampleRate); + + buffer->pitch = pitch; + } +} + +// Track audio buffer to linked list next position +void TrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer; + else + { + AUDIO.Buffer.last->next = buffer; + buffer->prev = AUDIO.Buffer.last; + } + + AUDIO.Buffer.last = buffer; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Untrack audio buffer from linked list +void UntrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next; + else buffer->prev->next = buffer->next; + + if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev; + else buffer->next->prev = buffer->prev; + + buffer->prev = NULL; + buffer->next = NULL; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Sounds loading and playing (.WAV) +//---------------------------------------------------------------------------------- + +// Load wave data from file +Wave LoadWave(const char *fileName) +{ + Wave wave = { 0 }; + + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading wave from memory data + wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, fileSize); + + RL_FREE(fileData); + } + + return wave; +} + +// Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Wave wave = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (TextIsEqual(fileExtLower, ".wav")) wave = LoadWAV(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (TextIsEqual(fileExtLower, ".ogg")) wave = LoadOGG(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (TextIsEqual(fileExtLower, ".flac")) wave = LoadFLAC(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (TextIsEqual(fileExtLower, ".mp3")) wave = LoadMP3(fileData, dataSize); +#endif + else TRACELOG(LOG_WARNING, "WAVE: File format not supported"); + + return wave; +} + +// Load sound from file +// NOTE: The entire file is loaded to memory to be played (no-streaming) +Sound LoadSound(const char *fileName) +{ + Wave wave = LoadWave(fileName); + + Sound sound = LoadSoundFromWave(wave); + + UnloadWave(wave); // Sound is loaded, we can unload wave + + return sound; +} + +// Load sound from wave data +// NOTE: Wave data must be unallocated manually +Sound LoadSoundFromWave(Wave wave) +{ + Sound sound = { 0 }; + + if (wave.data != NULL) + { + // When using miniaudio we need to do our own mixing. + // To simplify this we need convert the format of each sound to be consistent with + // the format used to open the playback AUDIO.System.device. We can do this two ways: + // + // 1) Convert the whole sound in one go at load time (here). + // 2) Convert the audio data in chunks at mixing time. + // + // First option has been selected, format conversion is done on the loading stage. + // The downside is that it uses more memory if the original sound is u8 or s16. + ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_uint32 frameCountIn = wave.sampleCount/wave.channels; + + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion"); + + AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + return sound; // early return to avoid dereferencing the audioBuffer null pointer + } + + frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion"); + + sound.sampleCount = frameCount*AUDIO_DEVICE_CHANNELS; + sound.stream.sampleRate = AUDIO.System.device.sampleRate; + sound.stream.sampleSize = 32; + sound.stream.channels = AUDIO_DEVICE_CHANNELS; + sound.stream.buffer = audioBuffer; + } + + return sound; +} + +// Unload wave data +void UnloadWave(Wave wave) +{ + if (wave.data != NULL) RL_FREE(wave.data); + + TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM"); +} + +// Unload sound +void UnloadSound(Sound sound) +{ + UnloadAudioBuffer(sound.stream.buffer); + + TRACELOG(LOG_INFO, "WAVE: Unloaded sound data from RAM"); +} + +// Update sound buffer with new data +void UpdateSound(Sound sound, const void *data, int samplesCount) +{ + if (sound.stream.buffer != NULL) + { + StopAudioBuffer(sound.stream.buffer); + + // TODO: May want to lock/unlock this since this data buffer is read at mixing time + memcpy(sound.stream.buffer->data, data, samplesCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.config.formatIn, sound.stream.buffer->converter.config.channelsIn)); + } +} + +// Export wave data to file +bool ExportWave(Wave wave, const char *fileName) +{ + bool success = false; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName); +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw sample data (without header) + // NOTE: It's up to the user to track wave parameters + success = SaveFileData(fileName, wave.data, wave.sampleCount*wave.sampleSize/8); + } + + if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName); + + return success; +} + +// Export wave sample data to code (.h) +bool ExportWaveAsCode(Wave wave, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + int waveDataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; + + // NOTE: Text data buffer size is estimated considering wave data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(6*waveDataSize + 2000, sizeof(char)); + + int bytesCount = 0; + bytesCount += sprintf(txtData + bytesCount, "\n//////////////////////////////////////////////////////////////////////////////////\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + bytesCount += sprintf(txtData + bytesCount, "// feedback and support: ray[at]raylib.com //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "//////////////////////////////////////////////////////////////////////////////////\n\n"); + + char varFileName[256] = { 0 }; +#if !defined(RAUDIO_STANDALONE) + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } +#else + strcpy(varFileName, fileName); +#endif + + bytesCount += sprintf(txtData + bytesCount, "// Wave data information\n"); + bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_COUNT %u\n", varFileName, wave.sampleCount); + bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate); + bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize); + bytesCount += sprintf(txtData + bytesCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels); + + // Write byte data as hexadecimal text + bytesCount += sprintf(txtData + bytesCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize); + for (int i = 0; i < waveDataSize - 1; i++) bytesCount += sprintf(txtData + bytesCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); + bytesCount += sprintf(txtData + bytesCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); + + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + return success; +} + +// Play a sound +void PlaySound(Sound sound) +{ + PlayAudioBuffer(sound.stream.buffer); +} + +// Play a sound in the multichannel buffer pool +void PlaySoundMulti(Sound sound) +{ + int index = -1; + unsigned int oldAge = 0; + int oldIndex = -1; + + // find the first non playing pool entry + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + if (AUDIO.MultiChannel.channels[i] > oldAge) + { + oldAge = AUDIO.MultiChannel.channels[i]; + oldIndex = i; + } + + if (!IsAudioBufferPlaying(AUDIO.MultiChannel.pool[i])) + { + index = i; + break; + } + } + + // If no none playing pool members can be index choose the oldest + if (index == -1) + { + TRACELOG(LOG_WARNING, "SOUND: Buffer pool is already full, count: %i", AUDIO.MultiChannel.poolCounter); + + if (oldIndex == -1) + { + // Shouldn't be able to get here... but just in case something odd happens! + TRACELOG(LOG_WARNING, "SOUND: Buffer pool could not determine oldest buffer not playing sound"); + return; + } + + index = oldIndex; + + // Just in case... + StopAudioBuffer(AUDIO.MultiChannel.pool[index]); + } + + // Experimentally mutex lock doesn't seem to be needed this makes sense + // as pool[index] isn't playing and the only stuff we're copying + // shouldn't be changing... + + AUDIO.MultiChannel.channels[index] = AUDIO.MultiChannel.poolCounter; + AUDIO.MultiChannel.poolCounter++; + + AUDIO.MultiChannel.pool[index]->volume = sound.stream.buffer->volume; + AUDIO.MultiChannel.pool[index]->pitch = sound.stream.buffer->pitch; + AUDIO.MultiChannel.pool[index]->looping = sound.stream.buffer->looping; + AUDIO.MultiChannel.pool[index]->usage = sound.stream.buffer->usage; + AUDIO.MultiChannel.pool[index]->isSubBufferProcessed[0] = false; + AUDIO.MultiChannel.pool[index]->isSubBufferProcessed[1] = false; + AUDIO.MultiChannel.pool[index]->sizeInFrames = sound.stream.buffer->sizeInFrames; + AUDIO.MultiChannel.pool[index]->data = sound.stream.buffer->data; + + PlayAudioBuffer(AUDIO.MultiChannel.pool[index]); +} + +// Stop any sound played with PlaySoundMulti() +void StopSoundMulti(void) +{ + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) StopAudioBuffer(AUDIO.MultiChannel.pool[i]); +} + +// Get number of sounds playing in the multichannel buffer pool +int GetSoundsPlaying(void) +{ + int counter = 0; + + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + if (IsAudioBufferPlaying(AUDIO.MultiChannel.pool[i])) counter++; + } + + return counter; +} + +// Pause a sound +void PauseSound(Sound sound) +{ + PauseAudioBuffer(sound.stream.buffer); +} + +// Resume a paused sound +void ResumeSound(Sound sound) +{ + ResumeAudioBuffer(sound.stream.buffer); +} + +// Stop reproducing a sound +void StopSound(Sound sound) +{ + StopAudioBuffer(sound.stream.buffer); +} + +// Check if a sound is playing +bool IsSoundPlaying(Sound sound) +{ + return IsAudioBufferPlaying(sound.stream.buffer); +} + +// Set volume for a sound +void SetSoundVolume(Sound sound, float volume) +{ + SetAudioBufferVolume(sound.stream.buffer, volume); +} + +// Set pitch for a sound +void SetSoundPitch(Sound sound, float pitch) +{ + SetAudioBufferPitch(sound.stream.buffer, pitch); +} + +// Convert wave data to desired format +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) +{ + ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + ma_uint32 frameCountIn = wave->sampleCount/wave->channels; + + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); + if (frameCount == 0) + { + TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion"); + return; + } + + void *data = RL_MALLOC(frameCount*channels*(sampleSize/8)); + + frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate); + if (frameCount == 0) + { + TRACELOG(LOG_WARNING, "WAVE: Failed format conversion"); + return; + } + + wave->sampleCount = frameCount*channels; + wave->sampleSize = sampleSize; + wave->sampleRate = sampleRate; + wave->channels = channels; + RL_FREE(wave->data); + wave->data = data; +} + +// Copy a wave to a new wave +Wave WaveCopy(Wave wave) +{ + Wave newWave = { 0 }; + + newWave.data = RL_MALLOC(wave.sampleCount*wave.sampleSize/8); + + if (newWave.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newWave.data, wave.data, wave.sampleCount*wave.sampleSize/8); + + newWave.sampleCount = wave.sampleCount; + newWave.sampleRate = wave.sampleRate; + newWave.sampleSize = wave.sampleSize; + newWave.channels = wave.channels; + } + + return newWave; +} + +// Crop a wave to defined samples range +// NOTE: Security check in case of out-of-range +void WaveCrop(Wave *wave, int initSample, int finalSample) +{ + if ((initSample >= 0) && (initSample < finalSample) && + (finalSample > 0) && ((unsigned int)finalSample < wave->sampleCount)) + { + int sampleCount = finalSample - initSample; + + void *data = RL_MALLOC(sampleCount*wave->sampleSize/8); + + memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->sampleSize/8); + + RL_FREE(wave->data); + wave->data = data; + } + else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds"); +} + +// Load samples data from wave as a floats array +// NOTE 1: Returned sample values are normalized to range [-1..1] +// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples() +float *LoadWaveSamples(Wave wave) +{ + float *samples = (float *)RL_MALLOC(wave.sampleCount*sizeof(float)); + + // NOTE: sampleCount is the total number of interlaced samples (including channels) + + for (unsigned int i = 0; i < wave.sampleCount; i++) + { + if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f; + else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32767.0f; + else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i]; + } + + return samples; +} + +// Unload samples data loaded with LoadWaveSamples() +void UnloadWaveSamples(float *samples) +{ + RL_FREE(samples); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Music loading and stream playing (.OGG) +//---------------------------------------------------------------------------------- + +// Load music stream from file +Music LoadMusicStream(const char *fileName) +{ + Music music = { 0 }; + bool musicLoaded = false; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) + { + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + bool success = drwav_init_file(ctxWav, fileName, NULL); + + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + + if (success) + { + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = InitAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.sampleCount = (unsigned int)ctxWav->totalPCMFrameCount*ctxWav->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (IsFileExtension(fileName, ".ogg")) + { + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + + if (music.ctxData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = InitAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (IsFileExtension(fileName, ".flac")) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_file(fileName, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = InitAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (IsFileExtension(fileName, ".mp3")) + { + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + int result = drmp3_init_file(ctxMp3, fileName, NULL); + + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + + if (result > 0) + { + music.stream = InitAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (IsFileExtension(fileName, ".xm")) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName); + + music.ctxType = MUSIC_MODULE_XM; + music.ctxData = ctxXm; + + if (result == 0) // XM AUDIO.System.context created successfully + { + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) + bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) + bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS); + music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (IsFileExtension(fileName, ".mod")) + { + jar_mod_context_t *ctxMod = RL_CALLOC(1, sizeof(jar_mod_context_t)); + jar_mod_init(ctxMod); + int result = jar_mod_load_file(ctxMod, fileName); + + music.ctxType = MUSIC_MODULE_MOD; + music.ctxData = ctxMod; + + if (result > 0) + { + // NOTE: Only stereo is supported for MOD + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS); + music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: [%s] Fileformat not supported", fileName); + + if (!musicLoaded) + { + if (false) { } + #if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } + #endif + + music.ctxData = NULL; + TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: [%s] Music file successfully loaded:", fileName); + TRACELOG(LOG_INFO, " > Total samples: %i", music.sampleCount); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + } + + return music; +} + +// extension including period ".mod" +Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize) +{ + Music music = { 0 }; + bool musicLoaded = false; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (TextIsEqual(fileExtLower, ".wav")) + { + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + + bool success = drwav_init_memory(ctxWav, (const void*)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + + if (success) + { + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = InitAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.sampleCount = (unsigned int)ctxWav->totalPCMFrameCount*ctxWav->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (TextIsEqual(fileExtLower, ".flac")) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = InitAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (TextIsEqual(fileExtLower, ".mp3")) + { + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + + if (success) + { + music.stream = InitAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (TextIsEqual(fileExtLower, ".ogg")) + { + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + //music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + music.ctxData = stb_vorbis_open_memory((const unsigned char*)data, dataSize, NULL, NULL); + + if (music.ctxData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = InitAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (TextIsEqual(fileExtLower, ".xm")) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_safe(&ctxXm, (const char*)data, dataSize, AUDIO.System.device.sampleRate); + if (result == 0) // XM AUDIO.System.context created successfully + { + music.ctxType = MUSIC_MODULE_XM; + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) + bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) + bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, bits, 2); + music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + + music.ctxData = ctxXm; + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (TextIsEqual(fileExtLower, ".mod")) + { + jar_mod_context_t *ctxMod = RL_MALLOC(sizeof(jar_mod_context_t)); + int result = 0; + + jar_mod_init(ctxMod); + + // copy data to allocated memory for default UnloadMusicStream + unsigned char *newData = RL_MALLOC(dataSize); + int it = dataSize/sizeof(unsigned char); + for (int i = 0; i < it; i++){ + newData[i] = data[i]; + } + + // Memory loaded version for jar_mod_load_file() + if (dataSize && dataSize < 32*1024*1024) + { + ctxMod->modfilesize = dataSize; + ctxMod->modfile = newData; + if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize; + } + + if (result > 0) + { + music.ctxType = MUSIC_MODULE_MOD; + + // NOTE: Only stereo is supported for MOD + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, 16, 2); + music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels + music.looping = true; // Looping enabled by default + musicLoaded = true; + + music.ctxData = ctxMod; + musicLoaded = true; + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: [%s] Fileformat not supported", fileType); + + if (!musicLoaded) + { + if (false) { } + #if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } + #endif + + music.ctxData = NULL; + TRACELOG(LOG_WARNING, "FILEIO: [%s] Music memory could not be opened", fileType); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: [%s] Music memory successfully loaded:", fileType); + TRACELOG(LOG_INFO, " > Total samples: %i", music.sampleCount); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + } + + return music; +} + +// Unload music stream +void UnloadMusicStream(Music music) +{ + CloseAudioStream(music.stream); + + if (music.ctxData != NULL) + { + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } +#endif + } +} + +// Start music playing (open stream) +void PlayMusicStream(Music music) +{ + if (music.stream.buffer != NULL) + { + // For music streams, we need to make sure we maintain the frame cursor position + // This is a hack for this section of code in UpdateMusicStream() + // NOTE: In case window is minimized, music stream is stopped, just make sure to + // play again on window restore: if (IsMusicPlaying(music)) PlayMusicStream(music); + ma_uint32 frameCursorPos = music.stream.buffer->frameCursorPos; + PlayAudioStream(music.stream); // WARNING: This resets the cursor position. + music.stream.buffer->frameCursorPos = frameCursorPos; + } +} + +// Pause music playing +void PauseMusicStream(Music music) +{ + PauseAudioStream(music.stream); +} + +// Resume music playing +void ResumeMusicStream(Music music) +{ + ResumeAudioStream(music.stream); +} + +// Stop music playing (close stream) +void StopMusicStream(Music music) +{ + StopAudioStream(music.stream); + + switch (music.ctxType) + { +#if defined(SUPPORT_FILEFORMAT_WAV) + case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, 0); break; +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, 0); break; +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, 0); break; +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break; +#endif + default: break; + } +} + +// Update (re-fill) music buffers if data already processed +void UpdateMusicStream(Music music) +{ + if (music.stream.buffer == NULL) + return; + + if (music.ctxType == MUSIC_MODULE_XM) + jar_xm_set_max_loop_count(music.ctxData, music.looping ? 0 : 1); + + bool streamEnding = false; + + unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; + + // NOTE: Using dynamic allocation because it could require more than 16KB + void *pcm = RL_CALLOC(subBufferSizeInFrames*music.stream.channels*music.stream.sampleSize/8, 1); + + int samplesCount = 0; // Total size of data streamed in L+R samples for xm floats, individual L or R for ogg shorts + + // TODO: Get the sampleLeft using totalFramesProcessed... but first, get total frames processed correctly... + //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; + int sampleLeft = music.sampleCount - (music.stream.buffer->totalFramesProcessed*music.stream.channels); + + if (music.ctxType == MUSIC_MODULE_XM && music.looping) sampleLeft = subBufferSizeInFrames*4; + + while (IsAudioStreamProcessed(music.stream)) + { + if ((sampleLeft/music.stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music.stream.channels; + else samplesCount = sampleLeft; + + switch (music.ctxType) + { + #if defined(SUPPORT_FILEFORMAT_WAV) + case MUSIC_AUDIO_WAV: + { + // NOTE: Returns the number of samples to process (not required) + if (music.stream.sampleSize == 16) drwav_read_pcm_frames_s16((drwav *)music.ctxData, samplesCount/music.stream.channels, (short *)pcm); + else if (music.stream.sampleSize == 32) drwav_read_pcm_frames_f32((drwav *)music.ctxData, samplesCount/music.stream.channels, (float *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: + { + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)pcm, samplesCount); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: + { + // NOTE: Returns the number of samples to process (not required) + drflac_read_pcm_frames_s16((drflac *)music.ctxData, samplesCount, (short *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: + { + // NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed + drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, samplesCount/music.stream.channels, (float *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: + { + switch (AUDIO_DEVICE_FORMAT) + { + case ma_format_f32: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples((jar_xm_context_t*)music.ctxData, (float*)pcm, samplesCount / 2); + break; + + case ma_format_s16: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples_16bit((jar_xm_context_t*)music.ctxData, (short*)pcm, samplesCount / 2); + break; + + case ma_format_u8: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples_8bit((jar_xm_context_t*)music.ctxData, (char*)pcm, samplesCount / 2); + break; + } + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: + { + // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 + jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)pcm, samplesCount/2, 0); + } break; + #endif + default: break; + } + + UpdateAudioStream(music.stream, pcm, samplesCount); + + if ((music.ctxType == MUSIC_MODULE_XM) || music.ctxType == MUSIC_MODULE_MOD) + { + if (samplesCount > 1) sampleLeft -= samplesCount/2; + else sampleLeft -= samplesCount; + } + else sampleLeft -= samplesCount; + + if (sampleLeft <= 0) + { + streamEnding = true; + break; + } + } + + // Free allocated pcm data + RL_FREE(pcm); + + // Reset audio stream for looping + if (streamEnding) + { + StopMusicStream(music); // Stop music (and reset) + if (music.looping) PlayMusicStream(music); // Play again + } + else + { + // NOTE: In case window is minimized, music stream is stopped, + // just make sure to play again on window restore + if (IsMusicPlaying(music)) PlayMusicStream(music); + } +} + +// Check if any music is playing +bool IsMusicPlaying(Music music) +{ + return IsAudioStreamPlaying(music.stream); +} + +// Set volume for music +void SetMusicVolume(Music music, float volume) +{ + SetAudioStreamVolume(music.stream, volume); +} + +// Set pitch for music +void SetMusicPitch(Music music, float pitch) +{ + SetAudioBufferPitch(music.stream.buffer, pitch); +} + +// Get music time length (in seconds) +float GetMusicTimeLength(Music music) +{ + float totalSeconds = 0.0f; + + totalSeconds = (float)music.sampleCount/(music.stream.sampleRate*music.stream.channels); + + return totalSeconds; +} + +// Get current music time played (in seconds) +float GetMusicTimePlayed(Music music) +{ +#if defined(SUPPORT_FILEFORMAT_XM) + if (music.ctxType == MUSIC_MODULE_XM) + { + uint64_t samples = 0; + jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &samples); + samples = samples % (music.sampleCount); + + return (float)(samples)/(music.stream.sampleRate*music.stream.channels); + } +#endif + float secondsPlayed = 0.0f; + if (music.stream.buffer != NULL) + { + //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; + unsigned int samplesPlayed = music.stream.buffer->totalFramesProcessed*music.stream.channels; + secondsPlayed = (float)samplesPlayed/(music.stream.sampleRate*music.stream.channels); + } + + return secondsPlayed; +} + +// Init audio stream (to stream audio pcm data) +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) +{ + AudioStream stream = { 0 }; + + stream.sampleRate = sampleRate; + stream.sampleSize = sampleSize; + stream.channels = channels; + + ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + // The size of a streaming buffer must be at least double the size of a period + unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; + unsigned int subBufferSize = GetAudioStreamBufferSizeDefault(); + + if (subBufferSize < periodSize) subBufferSize = periodSize; + + // Create a double audio buffer of defined size + stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); + + if (stream.buffer != NULL) + { + stream.buffer->looping = true; // Always loop for streaming buffers + TRACELOG(LOG_INFO, "STREAM: Initialized successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "STREAM: Failed to load audio buffer, stream could not be created"); + + return stream; +} + +// Close audio stream and free memory +void CloseAudioStream(AudioStream stream) +{ + UnloadAudioBuffer(stream.buffer); + + TRACELOG(LOG_INFO, "STREAM: Unloaded audio stream data from RAM"); +} + +// Update audio stream buffers with data +// NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue +// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioStreamProcessed() +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) +{ + if (stream.buffer != NULL) + { + if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]) + { + ma_uint32 subBufferToUpdate = 0; + + if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1]) + { + // Both buffers are available for updating. + // Update the first one and make sure the cursor is moved back to the front. + subBufferToUpdate = 0; + stream.buffer->frameCursorPos = 0; + } + else + { + // Just update whichever sub-buffer is processed. + subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1; + } + + ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2; + unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); + + // TODO: Get total frames processed on this buffer... DOES NOT WORK. + stream.buffer->totalFramesProcessed += subBufferSizeInFrames; + + // Does this API expect a whole buffer to be updated in one go? + // Assuming so, but if not will need to change this logic. + if (subBufferSizeInFrames >= (ma_uint32)samplesCount/stream.channels) + { + ma_uint32 framesToWrite = subBufferSizeInFrames; + + if (framesToWrite > ((ma_uint32)samplesCount/stream.channels)) framesToWrite = (ma_uint32)samplesCount/stream.channels; + + ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); + memcpy(subBuffer, data, bytesToWrite); + + // Any leftover frames should be filled with zeros. + ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; + + if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); + + stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false; + } + else TRACELOG(LOG_WARNING, "STREAM: Attempting to write too many frames to buffer"); + } + else TRACELOG(LOG_WARNING, "STREAM: Buffer not available for updating"); + } +} + +// Check if any audio stream buffers requires refill +bool IsAudioStreamProcessed(AudioStream stream) +{ + if (stream.buffer == NULL) return false; + + return (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]); +} + +// Play audio stream +void PlayAudioStream(AudioStream stream) +{ + PlayAudioBuffer(stream.buffer); +} + +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + PauseAudioBuffer(stream.buffer); +} + +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ResumeAudioBuffer(stream.buffer); +} + +// Check if audio stream is playing. +bool IsAudioStreamPlaying(AudioStream stream) +{ + return IsAudioBufferPlaying(stream.buffer); +} + +// Stop audio stream +void StopAudioStream(AudioStream stream) +{ + StopAudioBuffer(stream.buffer); +} + +// Set volume for audio stream (1.0 is max level) +void SetAudioStreamVolume(AudioStream stream, float volume) +{ + SetAudioBufferVolume(stream.buffer, volume); +} + +// Set pitch for audio stream (1.0 is base level) +void SetAudioStreamPitch(AudioStream stream, float pitch) +{ + SetAudioBufferPitch(stream.buffer, pitch); +} + +// Default size for new audio streams +void SetAudioStreamBufferSizeDefault(int size) +{ + AUDIO.Buffer.defaultSize = size; +} + +int GetAudioStreamBufferSizeDefault() +{ + // if the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate + if (AUDIO.Buffer.defaultSize == 0) + AUDIO.Buffer.defaultSize = AUDIO.System.device.sampleRate/30; + + return AUDIO.Buffer.defaultSize; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Log callback function +static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message) +{ + (void)pContext; + (void)pDevice; + + TRACELOG(LOG_WARNING, "miniaudio: %s", message); // All log messages from miniaudio are errors +} + +// Reads audio data from an AudioBuffer object in internal format. +static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount) +{ + ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames; + ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; + + if (currentSubBufferIndex > 1) return 0; + + // Another thread can update the processed state of buffers so + // we just take a copy here to try and avoid potential synchronization problems + bool isSubBufferProcessed[2]; + isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; + isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; + + ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.config.formatIn, audioBuffer->converter.config.channelsIn); + + // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0 + ma_uint32 framesRead = 0; + while (1) + { + // We break from this loop differently depending on the buffer's usage + // - For static buffers, we simply fill as much data as we can + // - For streaming buffers we only fill the halves of the buffer that are processed + // Unprocessed halves must keep their audio data in-tact + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + if (framesRead >= frameCount) break; + } + else + { + if (isSubBufferProcessed[currentSubBufferIndex]) break; + } + + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining == 0) break; + + ma_uint32 framesRemainingInOutputBuffer; + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + framesRemainingInOutputBuffer = audioBuffer->sizeInFrames - audioBuffer->frameCursorPos; + } + else + { + ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex; + framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); + } + + ma_uint32 framesToRead = totalFramesRemaining; + if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; + + memcpy((unsigned char *)framesOut + (framesRead*frameSizeInBytes), audioBuffer->data + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); + audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames; + framesRead += framesToRead; + + // If we've read to the end of the buffer, mark it as processed + if (framesToRead == framesRemainingInOutputBuffer) + { + audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; + isSubBufferProcessed[currentSubBufferIndex] = true; + + currentSubBufferIndex = (currentSubBufferIndex + 1)%2; + + // We need to break from this loop if we're not looping + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + } + } + + // Zero-fill excess + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining > 0) + { + memset((unsigned char *)framesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + + // For static buffers we can fill the remaining frames with silence for safety, but we don't want + // to report those frames as "read". The reason for this is that the caller uses the return value + // to know whether or not a non-looping sound has finished playback. + if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining; + } + + return framesRead; +} + +// Reads audio data from an AudioBuffer object in device format. Returned data will be in a format appropriate for mixing. +static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount) +{ + // What's going on here is that we're continuously converting data from the AudioBuffer's internal format to the mixing format, which + // should be defined by the output format of the data converter. We do this until frameCount frames have been output. The important + // detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output + // frames. This can be achieved with ma_data_converter_get_required_input_frame_count(). + ma_uint8 inputBuffer[4096]; + ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.config.formatIn, audioBuffer->converter.config.channelsIn); + + ma_uint32 totalOutputFramesProcessed = 0; + while (totalOutputFramesProcessed < frameCount) + { + ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed; + + ma_uint64 inputFramesToProcessThisIteration = ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration); + if (inputFramesToProcessThisIteration > inputBufferFrameCap) + { + inputFramesToProcessThisIteration = inputBufferFrameCap; + } + + float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.config.channelsOut); + + /* At this point we can convert the data to our mixing format. */ + ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration); /* Safe cast. */ + ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration; + ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration); + + totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; /* Safe cast. */ + + if (inputFramesProcessedThisIteration < inputFramesToProcessThisIteration) + { + break; /* Ran out of input data. */ + } + + /* This should never be hit, but will add it here for safety. Ensures we get out of the loop when no input nor output frames are processed. */ + if (inputFramesProcessedThisIteration == 0 && outputFramesProcessedThisIteration == 0) + { + break; + } + } + + return totalOutputFramesProcessed; +} + + +// Sending audio data to device callback function +// NOTE: All the mixing takes place here +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount) +{ + (void)pDevice; + + // Mixing is basically just an accumulation, we need to initialize the output buffer to 0 + memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format)); + + // Using a mutex here for thread-safety which makes things not real-time + // This is unlikely to be necessary for this project, but may want to consider how you might want to avoid this + ma_mutex_lock(&AUDIO.System.lock); + { + for (AudioBuffer *audioBuffer = AUDIO.Buffer.first; audioBuffer != NULL; audioBuffer = audioBuffer->next) + { + // Ignore stopped or paused sounds + if (!audioBuffer->playing || audioBuffer->paused) continue; + + ma_uint32 framesRead = 0; + + while (1) + { + if (framesRead >= frameCount) break; + + // Just read as much data as we can from the stream + ma_uint32 framesToRead = (frameCount - framesRead); + + while (framesToRead > 0) + { + float tempBuffer[1024]; // 512 frames for stereo + + ma_uint32 framesToReadRightNow = framesToRead; + if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS) + { + framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS; + } + + ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat(audioBuffer, tempBuffer, framesToReadRightNow); + if (framesJustRead > 0) + { + float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels); + float *framesIn = tempBuffer; + + MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer->volume); + + framesToRead -= framesJustRead; + framesRead += framesJustRead; + } + + if (!audioBuffer->playing) + { + framesRead = frameCount; + break; + } + + // If we weren't able to read all the frames we requested, break + if (framesJustRead < framesToReadRightNow) + { + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + else + { + // Should never get here, but just for safety, + // move the cursor position back to the start and continue the loop + audioBuffer->frameCursorPos = 0; + continue; + } + } + } + + // If for some reason we weren't able to read every frame we'll need to break from the loop + // Not doing this could theoretically put us into an infinite loop + if (framesToRead > 0) break; + } + } + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// This is the main mixing function. Mixing is pretty simple in this project - it's just an accumulation. +// NOTE: framesOut is both an input and an output. It will be initially filled with zeros outside of this function. +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume) +{ + for (ma_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) + { + for (ma_uint32 iChannel = 0; iChannel < AUDIO.System.device.playback.channels; ++iChannel) + { + float *frameOut = framesOut + (iFrame*AUDIO.System.device.playback.channels); + const float *frameIn = framesIn + (iFrame*AUDIO.System.device.playback.channels); + + frameOut[iChannel] += (frameIn[iChannel]*localVolume); + } + } +} + +#if defined(SUPPORT_FILEFORMAT_WAV) +// Load WAV file data into Wave structure +// NOTE: Using dr_wav library +static Wave LoadWAV(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + drwav wav = { 0 }; + + bool success = drwav_init_memory(&wav, fileData, fileSize, NULL); + + if (success) + { + wave.sampleCount = (unsigned int)wav.totalPCMFrameCount*wav.channels; + wave.sampleRate = wav.sampleRate; + wave.sampleSize = 16; // NOTE: We are forcing conversion to 16bit + wave.channels = wav.channels; + wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); + drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, wave.data); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data"); + + drwav_uninit(&wav); + + return wave; +} + +// Save wave data as WAV file +// NOTE: Using dr_wav library +static int SaveWAV(Wave wave, const char *fileName) +{ + int success = false; + + drwav wav = { 0 }; + drwav_data_format format = { 0 }; + format.container = drwav_container_riff; + format.format = DR_WAVE_FORMAT_PCM; + format.channels = wave.channels; + format.sampleRate = wave.sampleRate; + format.bitsPerSample = wave.sampleSize; + + void *fileData = NULL; + size_t fileDataSize = 0; + success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); + if (success) success = (int)drwav_write_pcm_frames(&wav, wave.sampleCount/wave.channels, wave.data); + drwav_result result = drwav_uninit(&wav); + + if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize); + + drwav_free(fileData, NULL); + + return success; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_OGG) +// Load OGG file data into Wave structure +// NOTE: Using stb_vorbis library +static Wave LoadOGG(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + + stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, fileSize, NULL, NULL); + + if (oggData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info(oggData); + + wave.sampleRate = info.sample_rate; + wave.sampleSize = 16; // 16 bit per sample (short) + wave.channels = info.channels; + wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData)*info.channels; // Independent by channel + + float totalSeconds = stb_vorbis_stream_length_in_seconds(oggData); + if (totalSeconds > 10) TRACELOG(LOG_WARNING, "WAVE: OGG audio length larger than 10 seconds (%f sec.), that's a big file in memory, consider music streaming", totalSeconds); + + wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); + + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.sampleCount); + TRACELOG(LOG_INFO, "WAVE: OGG data loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + + stb_vorbis_close(oggData); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); + + return wave; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_FLAC) +// Load FLAC file data into Wave structure +// NOTE: Using dr_flac library +static Wave LoadFLAC(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + + // Decode the entire FLAC file in one go + unsigned long long int totalFrameCount = 0; + wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, fileSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); + + if (wave.data != NULL) + { + wave.sampleCount = (unsigned int)totalFrameCount*wave.channels; + wave.sampleSize = 16; + + TRACELOG(LOG_INFO, "WAVE: FLAC data loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); + + return wave; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_MP3) +// Load MP3 file data into Wave structure +// NOTE: Using dr_mp3 library +static Wave LoadMP3(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + drmp3_config config = { 0 }; + + // Decode the entire MP3 file in one go + unsigned long long int totalFrameCount = 0; + wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, fileSize, &config, &totalFrameCount, NULL); + + if (wave.data != NULL) + { + wave.channels = config.channels; + wave.sampleRate = config.sampleRate; + wave.sampleCount = (int)totalFrameCount*wave.channels; + wave.sampleSize = 32; + + // NOTE: Only support up to 2 channels (mono, stereo) + // TODO: Really? + if (wave.channels > 2) TRACELOG(LOG_WARNING, "WAVE: MP3 channels number (%i) not supported", wave.channels); + + TRACELOG(LOG_INFO, "WAVE: MP3 file loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); + + return wave; +} +#endif + +// Some required functions for audio standalone module version +#if defined(RAUDIO_STANDALONE) +// Check file extension +static bool IsFileExtension(const char *fileName, const char *ext) +{ + bool result = false; + const char *fileExt; + + if ((fileExt = strrchr(fileName, '.')) != NULL) + { + if (strcmp(fileExt, ext) == 0) result = true; + } + + return result; +} + +// Load data from file into a buffer +static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) +{ + unsigned char *data = NULL; + *bytesRead = 0; + + if (fileName != NULL) + { + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); + + // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] + unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file); + *bytesRead = count; + + if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Save data to file from buffer +static bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) +{ + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), bytesToWrite, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != bytesToWrite) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); +} + +// Save text data to file (write), string must be '\0' terminated +static bool SaveFileText(const char *fileName, char *text) +{ + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = fprintf(file, "%s", text); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); +} +#endif + +#undef AudioBuffer diff --git a/raylib/raudio.h b/raylib/raudio.h new file mode 100644 index 0000000..cf8b767 --- /dev/null +++ b/raylib/raudio.h @@ -0,0 +1,198 @@ +/********************************************************************************************** +* +* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio +* +* FEATURES: +* - Manage audio device (init/close) +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* - Manage mixing channels +* - Manage raw audio context +* +* DEPENDENCIES: +* miniaudio.h - Audio device management lib (https://github.com/dr-soft/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to miniaudio library +* +* Joshua Reisenauer (github: @kd7tck) (2015) +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAUDIO_H +#define RAUDIO_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#ifndef __cplusplus +// Boolean type + #if !defined(_STDBOOL_H) + typedef enum { false, true } bool; + #define _STDBOOL_H + #endif +#endif + +// Wave type, defines audio wave data +typedef struct Wave { + unsigned int sampleCount; // Total number of samples + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + void *data; // Buffer data pointer +} Wave; + +typedef struct rAudioBuffer rAudioBuffer; + +// Audio stream type +// NOTE: Useful to create custom audio streams not bound to a specific file +typedef struct AudioStream { + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + + rAudioBuffer *buffer; // Pointer to internal data used by the audio system +} AudioStream; + +// Sound source type +typedef struct Sound { + unsigned int sampleCount; // Total number of samples + AudioStream stream; // Audio stream +} Sound; + +// Music stream type (audio file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct Music { + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type + + bool looping; // Music looping enable + unsigned int sampleCount; // Total number of samples + + AudioStream stream; // Audio stream +} Music; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +// Audio device management functions +void InitAudioDevice(void); // Initialize audio device and context +void CloseAudioDevice(void); // Close the audio device and context +bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +void SetMasterVolume(float volume); // Set master volume (listener) + +// Wave/Sound loading/unloading functions +Wave LoadWave(const char *fileName); // Load wave data from file +Sound LoadSound(const char *fileName); // Load sound from file +Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data +void UnloadWave(Wave wave); // Unload wave data +void UnloadSound(Sound sound); // Unload sound +void ExportWave(Wave wave, const char *fileName); // Export wave data to file +void ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h) + +// Wave/Sound management functions +void PlaySound(Sound sound); // Play a sound +void StopSound(Sound sound); // Stop playing a sound +void PauseSound(Sound sound); // Pause a sound +void ResumeSound(Sound sound); // Resume a paused sound +void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel +bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +Wave WaveCopy(Wave wave); // Copy a wave to a new wave +void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range +float *GetWaveData(Wave wave); // Get samples data from wave as a floats array + +// Music management functions +Music LoadMusicStream(const char *fileName); // Load music stream from file +Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize); // Load module music from data +void UnloadMusicStream(Music music); // Unload music stream +void PlayMusicStream(Music music); // Start music playing +void UpdateMusicStream(Music music); // Updates buffers for music streaming +void StopMusicStream(Music music); // Stop music playing +void PauseMusicStream(Music music); // Pause music playing +void ResumeMusicStream(Music music); // Resume playing paused music +bool IsMusicPlaying(Music music); // Check if music is playing +void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +float GetMusicTimeLength(Music music); // Get music time length (in seconds) +float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Init audio stream (to stream raw audio pcm data) +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data +void CloseAudioStream(AudioStream stream); // Close audio stream and free memory +bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +void PlayAudioStream(AudioStream stream); // Play audio stream +void PauseAudioStream(AudioStream stream); // Pause audio stream +void ResumeAudioStream(AudioStream stream); // Resume audio stream +bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +void StopAudioStream(AudioStream stream); // Stop audio stream +void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams + +#ifdef __cplusplus +} +#endif + +#endif // RAUDIO_H diff --git a/raylib/raylib.dll.rc b/raylib/raylib.dll.rc new file mode 100644 index 0000000..f4d90f4 --- /dev/null +++ b/raylib/raylib.dll.rc @@ -0,0 +1,27 @@ +GLFW_ICON ICON "raylib.ico" + +1 VERSIONINFO +FILEVERSION 3,7,0,0 +PRODUCTVERSION 3,7,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + //BLOCK "080904E4" // English UK + BLOCK "040904E4" // English US + BEGIN + //VALUE "CompanyName", "raylib technologies" + VALUE "FileDescription", "raylib dynamic library (www.raylib.com)" + VALUE "FileVersion", "3.7.0" + VALUE "InternalName", "raylib.dll" + VALUE "LegalCopyright", "(c) 2021 Ramon Santamaria (@raysan5)" + VALUE "OriginalFilename", "raylib.dll" + VALUE "ProductName", "raylib" + VALUE "ProductVersion", "3.7.0" + END + END + BLOCK "VarFileInfo" + BEGIN + //VALUE "Translation", 0x809, 1252 // English UK + VALUE "Translation", 0x409, 1252 // English US + END +END diff --git a/raylib/raylib.dll.rc.data b/raylib/raylib.dll.rc.data new file mode 100644 index 0000000..f4e5b3f Binary files /dev/null and b/raylib/raylib.dll.rc.data differ diff --git a/raylib/raylib.h b/raylib/raylib.h new file mode 100644 index 0000000..ead617c --- /dev/null +++ b/raylib/raylib.h @@ -0,0 +1,1520 @@ +/********************************************************************************************** +* +* raylib - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, UWP, Android, Raspberry Pi, HTML5. +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES2 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, XNA fonts, AngelCode fonts) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM, glTF) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* One default Texture2D is loaded on rlglInit() [rlgl] (OpenGL 3.3 or ES2) +* One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES (included): +* [core] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input (PLATFORM_DESKTOP) +* [rlgl] glad (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (PLATFORM_DESKTOP) +* [raudio] miniaudio (David Reid - github.com/dr-soft/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [core] msf_gif (Miles Fogle) for GIF recording +* [core] sinfl (Micha Mettke) for DEFLATE decompression algorythm +* [core] sdefl (Micha Mettke) for DEFLATE compression algorythm +* [textures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [textures] stb_image_write (Sean Barret) for image writting (BMP, TGA, PNG, JPG) +* [textures] stb_image_resize (Sean Barret) for image resizing algorithms +* [textures] stb_perlin (Sean Barret) for Perlin noise image generation +* [text] stb_truetype (Sean Barret) for ttf fonts loading +* [text] stb_rect_pack (Sean Barret) for rectangles packing +* [models] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [models] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [models] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include // Required for: va_list - Only used by TraceLogCallback + +#if defined(_WIN32) + // Microsoft attibutes to tell compiler that symbols are imported/exported from a .dll + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building raylib as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RLAPI // We are building or using raylib as a static library + #endif +#else + #define RLAPI // We are building or using raylib as a static library (or Linux shared library) +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#define DEG2RAD (PI/180.0f) +#define RAD2DEG (180.0f/PI) + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized from { } initializers. +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +// Temporal hacks to avoid breaking old codebases using +// deprecated raylib implementation or definitions +#define FormatText TextFormat +#define LoadText LoadFileText +#define GetExtension GetFileExtension +#define GetImageData LoadImageColors +#define FILTER_POINT TEXTURE_FILTER_POINT +#define FILTER_BILINEAR TEXTURE_FILTER_BILINEAR +#define MAP_DIFFUSE MATERIAL_MAP_DIFFUSE +#define UNCOMPRESSED_R8G8B8A8 PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + +//---------------------------------------------------------------------------------- +// Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum { false, true } bool; +#endif + +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; + +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; + +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; + +// Quaternion type, same as Vector4 +typedef Vector4 Quaternion; + +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; +} Matrix; + +// Color type, RGBA (32bit) +typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; +} Color; + +// Rectangle type +typedef struct Rectangle { + float x; + float y; + float width; + float height; +} Rectangle; + +// Image type, bpp always RGBA (32bit) +// NOTE: Data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture type +// NOTE: Data stored in GPU memory +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D type, same as Texture +typedef Texture Texture2D; + +// TextureCubemap type, actually, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture type, for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D type, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// N-Patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// Font character info +typedef struct CharInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} CharInfo; + +// Font type, includes texture and charSet array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int charsCount; // Number of characters + int charsPadding; // Padding around the chars + Texture2D texture; // Characters texture atlas + Rectangle *recs; // Characters rectangles in texture + CharInfo *chars; // Characters info data +} Font; + +#define SpriteFont Font // SpriteFont type fallback, defaults to Font + +// Camera type, defines a camera position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view apperture in Y (degrees) in perspective, used as near plane width in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D type, defines a 2d camera +typedef struct Camera2D { + Vector2 offset; // Camera offset (displacement from target) + Vector2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} Camera2D; + +// Vertex data definning a mesh +// NOTE: Data stored in CPU memory (and GPU) +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Default vertex data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex second texture coordinates (useful for lightmaps) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices;// Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + int *boneIds; // Vertex bone ids, up to 4 bones influence by vertex (skinning) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader type (generic) +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (MAX_SHADER_LOCATIONS) +} Shader; + +// Material texture map +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material type (generic) +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transformation properties +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone information +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Model type +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) +} Model; + +// Model animation +typedef struct ModelAnimation { + int boneCount; // Number of bones + int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) + Transform **framePoses; // Poses array by frame +} ModelAnimation; + +// Ray type (useful for raycast) +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction +} Ray; + +// Raycast hit information +typedef struct RayHitInfo { + bool hit; // Did the ray hit something? + float distance; // Distance to nearest hit + Vector3 position; // Position of nearest hit + Vector3 normal; // Surface normal of hit +} RayHitInfo; + +// Bounding box type +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave type, defines audio wave data +typedef struct Wave { + unsigned int sampleCount; // Total number of samples + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + void *data; // Buffer data pointer +} Wave; + +typedef struct rAudioBuffer rAudioBuffer; + +// Audio stream type +// NOTE: Useful to create custom audio streams not bound to a specific file +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) +} AudioStream; + +// Sound source type +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int sampleCount; // Total number of samples +} Sound; + +// Music stream type (audio file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int sampleCount; // Total number of samples + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float vScreenCenter; // Screen center in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VR Stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts +typedef enum { + KEY_NULL = 0, + // Alphanumeric keys + KEY_APOSTROPHE = 39, + KEY_COMMA = 44, + KEY_MINUS = 45, + KEY_PERIOD = 46, + KEY_SLASH = 47, + KEY_ZERO = 48, + KEY_ONE = 49, + KEY_TWO = 50, + KEY_THREE = 51, + KEY_FOUR = 52, + KEY_FIVE = 53, + KEY_SIX = 54, + KEY_SEVEN = 55, + KEY_EIGHT = 56, + KEY_NINE = 57, + KEY_SEMICOLON = 59, + KEY_EQUAL = 61, + KEY_A = 65, + KEY_B = 66, + KEY_C = 67, + KEY_D = 68, + KEY_E = 69, + KEY_F = 70, + KEY_G = 71, + KEY_H = 72, + KEY_I = 73, + KEY_J = 74, + KEY_K = 75, + KEY_L = 76, + KEY_M = 77, + KEY_N = 78, + KEY_O = 79, + KEY_P = 80, + KEY_Q = 81, + KEY_R = 82, + KEY_S = 83, + KEY_T = 84, + KEY_U = 85, + KEY_V = 86, + KEY_W = 87, + KEY_X = 88, + KEY_Y = 89, + KEY_Z = 90, + + // Function keys + KEY_SPACE = 32, + KEY_ESCAPE = 256, + KEY_ENTER = 257, + KEY_TAB = 258, + KEY_BACKSPACE = 259, + KEY_INSERT = 260, + KEY_DELETE = 261, + KEY_RIGHT = 262, + KEY_LEFT = 263, + KEY_DOWN = 264, + KEY_UP = 265, + KEY_PAGE_UP = 266, + KEY_PAGE_DOWN = 267, + KEY_HOME = 268, + KEY_END = 269, + KEY_CAPS_LOCK = 280, + KEY_SCROLL_LOCK = 281, + KEY_NUM_LOCK = 282, + KEY_PRINT_SCREEN = 283, + KEY_PAUSE = 284, + KEY_F1 = 290, + KEY_F2 = 291, + KEY_F3 = 292, + KEY_F4 = 293, + KEY_F5 = 294, + KEY_F6 = 295, + KEY_F7 = 296, + KEY_F8 = 297, + KEY_F9 = 298, + KEY_F10 = 299, + KEY_F11 = 300, + KEY_F12 = 301, + KEY_LEFT_SHIFT = 340, + KEY_LEFT_CONTROL = 341, + KEY_LEFT_ALT = 342, + KEY_LEFT_SUPER = 343, + KEY_RIGHT_SHIFT = 344, + KEY_RIGHT_CONTROL = 345, + KEY_RIGHT_ALT = 346, + KEY_RIGHT_SUPER = 347, + KEY_KB_MENU = 348, + KEY_LEFT_BRACKET = 91, + KEY_BACKSLASH = 92, + KEY_RIGHT_BRACKET = 93, + KEY_GRAVE = 96, + + // Keypad keys + KEY_KP_0 = 320, + KEY_KP_1 = 321, + KEY_KP_2 = 322, + KEY_KP_3 = 323, + KEY_KP_4 = 324, + KEY_KP_5 = 325, + KEY_KP_6 = 326, + KEY_KP_7 = 327, + KEY_KP_8 = 328, + KEY_KP_9 = 329, + KEY_KP_DECIMAL = 330, + KEY_KP_DIVIDE = 331, + KEY_KP_MULTIPLY = 332, + KEY_KP_SUBTRACT = 333, + KEY_KP_ADD = 334, + KEY_KP_ENTER = 335, + KEY_KP_EQUAL = 336, + // Android key buttons + KEY_BACK = 4, + KEY_MENU = 82, + KEY_VOLUME_UP = 24, + KEY_VOLUME_DOWN = 25 +} KeyboardKey; + +// Mouse buttons +typedef enum { + MOUSE_LEFT_BUTTON = 0, + MOUSE_RIGHT_BUTTON = 1, + MOUSE_MIDDLE_BUTTON = 2 +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, + MOUSE_CURSOR_ARROW = 1, + MOUSE_CURSOR_IBEAM = 2, + MOUSE_CURSOR_CROSSHAIR = 3, + MOUSE_CURSOR_POINTING_HAND = 4, + MOUSE_CURSOR_RESIZE_EW = 5, // The horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // The vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // The top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omni-directional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + // This is here just for error checking + GAMEPAD_BUTTON_UNKNOWN = 0, + + // This is normally a DPAD + GAMEPAD_BUTTON_LEFT_FACE_UP, + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, + GAMEPAD_BUTTON_LEFT_FACE_DOWN, + GAMEPAD_BUTTON_LEFT_FACE_LEFT, + + // This normally corresponds with PlayStation and Xbox controllers + // XBOX: [Y,X,A,B] + // PS3: [Triangle,Square,Cross,Circle] + // No support for 6 button controllers though.. + GAMEPAD_BUTTON_RIGHT_FACE_UP, + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, + + // Triggers + GAMEPAD_BUTTON_LEFT_TRIGGER_1, + GAMEPAD_BUTTON_LEFT_TRIGGER_2, + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, + + // These are buttons in the center of the gamepad + GAMEPAD_BUTTON_MIDDLE_LEFT, // PS3 Select + GAMEPAD_BUTTON_MIDDLE, // PS Button/XBOX Button + GAMEPAD_BUTTON_MIDDLE_RIGHT, // PS3 Start + + // These are the joystick press in buttons + GAMEPAD_BUTTON_LEFT_THUMB, + GAMEPAD_BUTTON_RIGHT_THUMB +} GamepadButton; + +// Gamepad axis +typedef enum { + // Left stick + GAMEPAD_AXIS_LEFT_X = 0, + GAMEPAD_AXIS_LEFT_Y = 1, + + // Right stick + GAMEPAD_AXIS_RIGHT_X = 2, + GAMEPAD_AXIS_RIGHT_Y = 3, + + // Pressure levels for the back triggers + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // [1..-1] (pressure-level) + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // [1..-1] (pressure-level) +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // MATERIAL_MAP_DIFFUSE + MATERIAL_MAP_METALNESS = 1, // MATERIAL_MAP_SPECULAR + MATERIAL_MAP_NORMAL = 2, + MATERIAL_MAP_ROUGHNESS = 3, + MATERIAL_MAP_OCCLUSION, + MATERIAL_MAP_EMISSION, + MATERIAL_MAP_HEIGHT, + MATERIAL_MAP_BRDG, + MATERIAL_MAP_CUBEMAP, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MATERIAL_MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MATERIAL_MAP_PREFILTER // NOTE: Uses GL_TEXTURE_CUBE_MAP +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, + SHADER_LOC_VERTEX_TEXCOORD01, + SHADER_LOC_VERTEX_TEXCOORD02, + SHADER_LOC_VERTEX_NORMAL, + SHADER_LOC_VERTEX_TANGENT, + SHADER_LOC_VERTEX_COLOR, + SHADER_LOC_MATRIX_MVP, + SHADER_LOC_MATRIX_VIEW, + SHADER_LOC_MATRIX_PROJECTION, + SHADER_LOC_MATRIX_MODEL, + SHADER_LOC_MATRIX_NORMAL, + SHADER_LOC_VECTOR_VIEW, + SHADER_LOC_COLOR_DIFFUSE, + SHADER_LOC_COLOR_SPECULAR, + SHADER_LOC_COLOR_AMBIENT, + SHADER_LOC_MAP_ALBEDO, // SHADER_LOC_MAP_DIFFUSE + SHADER_LOC_MAP_METALNESS, // SHADER_LOC_MAP_SPECULAR + SHADER_LOC_MAP_NORMAL, + SHADER_LOC_MAP_ROUGHNESS, + SHADER_LOC_MAP_OCCLUSION, + SHADER_LOC_MAP_EMISSION, + SHADER_LOC_MAP_HEIGHT, + SHADER_LOC_MAP_CUBEMAP, + SHADER_LOC_MAP_IRRADIANCE, + SHADER_LOC_MAP_PREFILTER, + SHADER_LOC_MAP_BRDF +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, + SHADER_UNIFORM_VEC2, + SHADER_UNIFORM_VEC3, + SHADER_UNIFORM_VEC4, + SHADER_UNIFORM_INT, + SHADER_UNIFORM_IVEC2, + SHADER_UNIFORM_IVEC3, + SHADER_UNIFORM_IVEC4, + SHADER_UNIFORM_SAMPLER2D +} ShaderUniformDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_RGB565_BE, // 16 bpp (big endian) + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel aproximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by an horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE, // Layout is defined by a 4x3 cross with cubemap faces + CUBEMAP_LAYOUT_PANORAMA // Layout is defined by a panorama image (equirectangular map) +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_CUSTOM // Belnd textures using custom src/dst factors (use rlSetBlendMode()) +} BlendMode; + +// Gestures +// NOTE: It could be used as flags to enable only some gestures +typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP = 1, + GESTURE_DOUBLETAP = 2, + GESTURE_HOLD = 4, + GESTURE_DRAG = 8, + GESTURE_SWIPE_RIGHT = 16, + GESTURE_SWIPE_LEFT = 32, + GESTURE_SWIPE_UP = 64, + GESTURE_SWIPE_DOWN = 128, + GESTURE_PINCH_IN = 256, + GESTURE_PINCH_OUT = 512 +} Gestures; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, + CAMERA_FREE, + CAMERA_ORBITAL, + CAMERA_FIRST_PERSON, + CAMERA_THIRD_PERSON +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, + CAMERA_ORTHOGRAPHIC +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: This callbacks are intended for advance users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char* (*LoadFileDataCallback)(const char* fileName, unsigned int* bytesRead); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, unsigned int bytesToWrite); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char* fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data + + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP) +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) +RLAPI void SetWindowIcon(Image image); // Set icon for window (only PLATFORM_DESKTOP) +RLAPI void SetWindowTitle(const char *title); // Set title for window (only PLATFORM_DESKTOP) +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen (only PLATFORM_DESKTOP) +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window (fullscreen mode) +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current connected monitor +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (max available by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (max available by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the primary monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content + +// Cursor-related functions +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disables cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the current screen. + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) +RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing +RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Initialize 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // Ends 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Initializes 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Initializes render texture for drawing +RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Returns a ray trace from mouse position +RLAPI Matrix GetCameraMatrix(Camera camera); // Returns camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Returns camera 2d transform matrix +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Returns the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Returns size position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Returns the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Returns the world space position for a 2d camera screen space position + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI int GetFPS(void); // Returns current FPS +RLAPI float GetFrameTime(void); // Returns time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Returns elapsed time in seconds since InitWindow() + +// Misc. functions +RLAPI int GetRandomValue(int min, int max); // Returns a random value between min and max (both included) +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) + +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advance users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +// Files management functions +RLAPI unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(unsigned char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext);// Check file extension (including point: .png, .wav) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: ".png") +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI char **GetDirectoryFiles(const char *dirPath, int *count); // Get filenames in a directory path (memory should be freed) +RLAPI void ClearDirectoryFiles(void); // Clear directory files paths buffers (free memory) +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window +RLAPI char **GetDroppedFiles(int *count); // Get dropped files names (memory should be freed) +RLAPI void ClearDroppedFiles(void); // Clear dropped files paths buffer (free memory) +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) + +RLAPI unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength); // Compress data (DEFLATE algorithm) +RLAPI unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength); // Decompress data (DEFLATE algorithm) + +// Persistent storage management +RLAPI bool SaveStorageValue(unsigned int position, int value); // Save integer value to storage file (to defined position), returns true on success +RLAPI int LoadStorageValue(unsigned int position); // Load integer value from storage file (from defined position) + +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Detect if a key has been pressed once +RLAPI bool IsKeyDown(int key); // Detect if a key is being pressed +RLAPI bool IsKeyReleased(int key); // Detect if a key has been released once +RLAPI bool IsKeyUp(int key); // Detect if a key is NOT being pressed +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available +RLAPI bool IsGamepadName(int gamepad, const char *name); // Check gamepad name (if available) +RLAPI const char *GetGamepadName(int gamepad); // Return gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Detect if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Detect if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Detect if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Return gamepad axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Return axis movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Detect if a mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Detect if a mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Detect if a mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Detect if a mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Returns mouse position X +RLAPI int GetMouseY(void); // Returns mouse position Y +RLAPI Vector2 GetMousePosition(void); // Returns mouse position XY +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Returns mouse wheel movement Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Returns touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Returns touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Returns touch position XY for a touch point index (relative to screen size) + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: gestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI int GetTouchPointsCount(void); // Get touch points count +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: camera) +//------------------------------------------------------------------------------------ +RLAPI void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) +RLAPI void UpdateCamera(Camera *camera); // Update camera position for selected mode + +RLAPI void SetCameraPanControl(int keyPan); // Set camera pan key to combine with mouse movement (free camera) +RLAPI void SetCameraAltControl(int keyAlt); // Set camera alt key to combine with mouse movement (free camera) +RLAPI void SetCameraSmoothZoomControl(int keySmoothZoom); // Set camera smooth zoom key to combine with mouse (free camera) +RLAPI void SetCameraMoveControls(int keyFront, int keyBack, int keyRight, int keyLeft, int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel (Vector version) +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (Vector version) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line defining thickness +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line using cubic-bezier curves in-out +RLAPI void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color); //Draw line using quadratic bezier curves with a control point +RLAPI void DrawLineStrip(Vector2 *points, int pointsCount, Color color); // Draw lines sequence +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color); // Draw rectangle outline with extended parameters +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) +RLAPI void DrawTriangleFan(Vector2 *points, int pointsCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: This functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. ".png" +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientV(int width, int height, Color top, Color bottom); // Generate image: vertical gradient +RLAPI Image GenImageGradientH(int width, int height, Color left, Color right); // Generate image: horizontal gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm. Bigger tileSize means bigger cells + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Generate all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorsCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw circle within an image (Vector version) +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data +RLAPI Image GetTextureData(Texture2D texture); // Get pixel data from GPU texture and return an Image +RLAPI Image GetScreenData(void); // Get pixel data from screen buffer and return an Image (screenshot) + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint); // Draw texture quad with tiling and offset parameters +RLAPI void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint); // Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely +RLAPI void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointsCount, Color tint); // Draw a textured polygon + +// Color/pixel related functions +RLAPI Color Fade(Color color, float alpha); // Returns color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Returns hexadecimal value for a Color +RLAPI Vector4 ColorNormalize(Color color); // Returns Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Returns Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Returns HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Returns a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color ColorAlpha(Color color, float alpha); // Returns color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Returns src alpha-blended into dst color with tint +RLAPI Color GetColor(int hexValue); // Get Color structure from hexadecimal value +RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount); // Load font from file with extended parameters +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount); // Load font from memory buffer, fileType refers to extension: i.e. ".ttf" +RLAPI CharInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const CharInfo *chars, Rectangle **recs, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(CharInfo *chars, int charsCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint); // Draw text using font inside rectangle limits +RLAPI void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, + int selectStart, int selectLength, Color selectTint, Color selectBackTint); // Draw text using font inside rectangle limits with support for text selection +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) + +// Text misc. functions +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get index position for a unicode character on font + +// Text strings management functions (no utf8 strings, only byte chars) +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI char *TextReplace(char *text, const char *replace, const char *by); // Replace text string (memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (memory must be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) +RLAPI char *TextToUtf8(int *codepoints, int length); // Encode text codepoint into utf8 text (memory must be freed!) + +// UTF8 text strings management functions +RLAPI int *GetCodepoints(const char *text, int *count); // Get all codepoints in a string, codepoints count returned by parameters +RLAPI int GetCodepointsCount(const char *text); // Get total number of characters (codepoints) in a UTF8 encoded string +RLAPI int GetNextCodepoint(const char *text, int *bytesProcessed); // Returns next codepoint in a UTF8 encoded string; 0x3f('?') is returned on failure +RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength); // Encode codepoint into utf8 text (char array length returned as parameter) + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleStrip3D(Vector3 *points, int pointsCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color); // Draw cube textured +RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere +RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters +RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model loading/unloading functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI void UnloadModelKeepMeshes(Model model); // Unload model (but not meshes) from memory (RAM and/or VRAM) + +// Mesh loading/unloading functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation* animations, unsigned int count); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Mesh manipulation functions +RLAPI BoundingBox MeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void MeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI void MeshBinormals(Mesh *mesh); // Compute mesh binormals + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 center, float size, Color tint); // Draw a billboard texture defined by source + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Detect collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Detect collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Detect collision between box and sphere +RLAPI bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius); // Detect collision between ray and sphere +RLAPI bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint); // Detect collision between ray and sphere, returns collision point +RLAPI bool CheckCollisionRayBox(Ray ray, BoundingBox box); // Detect collision between ray and box +RLAPI RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayHitInfo GetCollisionRayModel(Ray ray, Model model); // Get collision info between ray and model +RLAPI RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight); // Get collision info between ray and ground plane (Y-normal plane) + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +RLAPI void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +RLAPI int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel +RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a floats array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize); // Load music stream from data +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Init audio stream (to stream raw audio pcm data) +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data +RLAPI void CloseAudioStream(AudioStream stream); // Close audio stream and free memory +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H diff --git a/raylib/raylib.ico b/raylib/raylib.ico new file mode 100644 index 0000000..0cedcc5 Binary files /dev/null and b/raylib/raylib.ico differ diff --git a/raylib/raylib.rc b/raylib/raylib.rc new file mode 100644 index 0000000..4c500b2 --- /dev/null +++ b/raylib/raylib.rc @@ -0,0 +1,27 @@ +GLFW_ICON ICON "raylib.ico" + +1 VERSIONINFO +FILEVERSION 3,7,0,0 +PRODUCTVERSION 3,7,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + //BLOCK "080904E4" // English UK + BLOCK "040904E4" // English US + BEGIN + //VALUE "CompanyName", "raylib technologies" + VALUE "FileDescription", "raylib application (www.raylib.com)" + VALUE "FileVersion", "3.7.0" + VALUE "InternalName", "raylib app" + VALUE "LegalCopyright", "(c) 2021 Ramon Santamaria (@raysan5)" + //VALUE "OriginalFilename", "raylib_app.exe" + VALUE "ProductName", "raylib app" + VALUE "ProductVersion", "3.7.0" + END + END + BLOCK "VarFileInfo" + BEGIN + //VALUE "Translation", 0x809, 1252 // English UK + VALUE "Translation", 0x409, 1252 // English US + END +END diff --git a/raylib/raylib.rc.data b/raylib/raylib.rc.data new file mode 100644 index 0000000..4da6b88 Binary files /dev/null and b/raylib/raylib.rc.data differ diff --git a/raylib/raymath.h b/raylib/raymath.h new file mode 100644 index 0000000..e396d5a --- /dev/null +++ b/raylib/raymath.h @@ -0,0 +1,1546 @@ +/********************************************************************************************** +* +* raymath v1.2 - Math functions to work with Vector3, Matrix and Quaternions +* +* CONFIGURATION: +* +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYMATH_HEADER_ONLY +* Define static inline functions code, so #include header suffices for use. +* This may use up lots of memory. +* +* #define RAYMATH_STANDALONE +* Avoid raylib.h header inclusion in this file. +* Vector3 and Matrix data types are defined internally in raymath module. +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +//#define RAYMATH_STANDALONE // NOTE: To use raymath as standalone lib, just uncomment this line +//#define RAYMATH_HEADER_ONLY // NOTE: To compile functions as static inline, uncomment this line + +#ifndef RAYMATH_STANDALONE + #include "raylib.h" // Required for structs: Vector3, Matrix +#endif + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_HEADER_ONLY) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_HEADER_ONLY is contradictory" +#endif + +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMDEF __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll). + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMDEF __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RMDEF extern inline // Provide external definition + #endif +#elif defined(RAYMATH_HEADER_ONLY) + #define RMDEF static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMDEF static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMDEF inline // Functions may be inlined or external definition used + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Return float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Return float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +#if defined(RAYMATH_STANDALONE) + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Vector4 type + typedef struct Vector4 { + float x; + float y; + float z; + float w; + } Vector4; + + // Quaternion type + typedef Vector4 Quaternion; + + // Matrix type (OpenGL style 4x4 - right handed, column major) + typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; + } Matrix; +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +typedef struct float3 { float v[3]; } float3; +typedef struct float16 { float v[16]; } float16; + +#include // Required for: sinf(), cosf(), sqrtf(), tan(), fabs() + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMDEF float Clamp(float value, float min, float max) +{ + const float res = value < min ? min : value; + return res > max ? max : res; +} + +// Calculate linear interpolation between two floats +RMDEF float Lerp(float start, float end, float amount) +{ + return start + amount*(end - start); +} + +// Normalize input value within input range +RMDEF float Normalize(float value, float start, float end) +{ + return (value - start)/(end - start); +} + +// Remap input value within input range to output range +RMDEF float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + return (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMDEF Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + return result; +} + +// Vector with components value 1.0f +RMDEF Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + return result; +} + +// Add two vectors (v1 + v2) +RMDEF Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + return result; +} + +// Add vector and float value +RMDEF Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + return result; +} + +// Subtract two vectors (v1 - v2) +RMDEF Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + return result; +} + +// Subtract vector by float value +RMDEF Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + return result; +} + +// Calculate vector length +RMDEF float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + return result; +} + +// Calculate vector square length +RMDEF float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + return result; +} + +// Calculate two vectors dot product +RMDEF float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + return result; +} + +// Calculate distance between two vectors +RMDEF float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + return result; +} + +// Calculate angle from two vectors in X-axis +RMDEF float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); + if (result < 0) result += 360.0f; + return result; +} + +// Scale vector (multiply by value) +RMDEF Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + return result; +} + +// Multiply vector by vector +RMDEF Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + return result; +} + +// Negate vector +RMDEF Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + return result; +} + +// Divide vector by vector +RMDEF Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + return result; +} + +// Normalize provided vector +RMDEF Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = Vector2Scale(v, 1/Vector2Length(v)); + return result; +} + +// Calculate linear interpolation between two vectors +RMDEF Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMDEF Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = Vector2DotProduct(v, normal); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Rotate Vector by float in Degrees. +RMDEF Vector2 Vector2Rotate(Vector2 v, float degs) +{ + float rads = degs*DEG2RAD; + Vector2 result = {v.x*cosf(rads) - v.y*sinf(rads) , v.x*sinf(rads) + v.y*cosf(rads) }; + return result; +} + +// Move Vector towards target +RMDEF Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) result = target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMDEF Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + return result; +} + +// Vector with components value 1.0f +RMDEF Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + return result; +} + +// Add two vectors +RMDEF Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + return result; +} + +// Add vector and float value +RMDEF Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + return result; +} + +// Subtract two vectors +RMDEF Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + return result; +} + +// Subtract vector by float value +RMDEF Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + return result; +} + +// Multiply vector by scalar +RMDEF Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + return result; +} + +// Multiply vector by vector +RMDEF Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + return result; +} + +// Calculate two vectors cross product +RMDEF Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + return result; +} + +// Calculate one vector perpendicular vector +RMDEF Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = (float) fabs(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabs(v.y) < min) + { + min = (float) fabs(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabs(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + result = Vector3CrossProduct(v, cardinalAxis); + + return result; +} + +// Calculate vector length +RMDEF float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + return result; +} + +// Calculate vector square length +RMDEF float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + return result; +} + +// Calculate two vectors dot product +RMDEF float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + return result; +} + +// Calculate distance between two vectors +RMDEF float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + float result = sqrtf(dx*dx + dy*dy + dz*dz); + return result; +} + +// Negate provided vector (invert direction) +RMDEF Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + return result; +} + +// Divide vector by vector +RMDEF Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + return result; +} + +// Normalize provided vector +RMDEF Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length, ilength; + length = Vector3Length(v); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMDEF void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + *v1 = Vector3Normalize(*v1); + Vector3 vn = Vector3CrossProduct(*v1, *v2); + vn = Vector3Normalize(vn); + *v2 = Vector3CrossProduct(vn, *v1); +} + +// Transforms a Vector3 by a given Matrix +RMDEF Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMDEF Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Calculate linear interpolation between two vectors +RMDEF Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate reflected vector to normal +RMDEF Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*( DotProduct[ I,N] )) + + Vector3 result = { 0 }; + + float dotProduct = Vector3DotProduct(v, normal); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Return min value for each pair of components +RMDEF Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Return max value for each pair of components +RMDEF Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMDEF Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + //Vector v0 = b - a, v1 = c - a, v2 = p - a; + + Vector3 v0 = Vector3Subtract(b, a); + Vector3 v1 = Vector3Subtract(c, a); + Vector3 v2 = Vector3Subtract(p, a); + float d00 = Vector3DotProduct(v0, v0); + float d01 = Vector3DotProduct(v0, v1); + float d11 = Vector3DotProduct(v1, v1); + float d20 = Vector3DotProduct(v2, v0); + float d21 = Vector3DotProduct(v2, v1); + + float denom = d00*d11 - d01*d01; + + Vector3 result = { 0 }; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Returns Vector3 as float array +RMDEF float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMDEF float MatrixDeterminant(Matrix mat) +{ + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + return result; +} + +// Returns the trace of the matrix (sum of the values along the diagonal) +RMDEF float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + return result; +} + +// Transposes provided matrix +RMDEF Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMDEF Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Normalize provided matrix +RMDEF Matrix MatrixNormalize(Matrix mat) +{ + Matrix result = { 0 }; + + float det = MatrixDeterminant(mat); + + result.m0 = mat.m0/det; + result.m1 = mat.m1/det; + result.m2 = mat.m2/det; + result.m3 = mat.m3/det; + result.m4 = mat.m4/det; + result.m5 = mat.m5/det; + result.m6 = mat.m6/det; + result.m7 = mat.m7/det; + result.m8 = mat.m8/det; + result.m9 = mat.m9/det; + result.m10 = mat.m10/det; + result.m11 = mat.m11/det; + result.m12 = mat.m12/det; + result.m13 = mat.m13/det; + result.m14 = mat.m14/det; + result.m15 = mat.m15/det; + + return result; +} + +// Returns identity matrix +RMDEF Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMDEF Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = MatrixIdentity(); + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMDEF Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = MatrixIdentity(); + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Returns two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMDEF Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Returns translation matrix +RMDEF Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMDEF Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Returns x-rotation matrix (angle in radians) +RMDEF Matrix MatrixRotateX(float angle) +{ + Matrix result = MatrixIdentity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = -sinres; + result.m9 = sinres; + result.m10 = cosres; + + return result; +} + +// Returns y-rotation matrix (angle in radians) +RMDEF Matrix MatrixRotateY(float angle) +{ + Matrix result = MatrixIdentity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = sinres; + result.m8 = -sinres; + result.m10 = cosres; + + return result; +} + +// Returns z-rotation matrix (angle in radians) +RMDEF Matrix MatrixRotateZ(float angle) +{ + Matrix result = MatrixIdentity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = -sinres; + result.m4 = sinres; + result.m5 = cosres; + + return result; +} + + +// Returns xyz-rotation matrix (angles in radians) +RMDEF Matrix MatrixRotateXYZ(Vector3 ang) +{ + Matrix result = MatrixIdentity(); + + float cosz = cosf(-ang.z); + float sinz = sinf(-ang.z); + float cosy = cosf(-ang.y); + float siny = sinf(-ang.y); + float cosx = cosf(-ang.x); + float sinx = sinf(-ang.x); + + result.m0 = cosz*cosy; + result.m4 = (cosz*siny*sinx) - (sinz*cosx); + result.m8 = (cosz*siny*cosx) + (sinz*sinx); + + result.m1 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m9 = (sinz*siny*cosx) - (cosz*sinx); + + result.m2 = -siny; + result.m6 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Returns zyx-rotation matrix (angles in radians) +RMDEF Matrix MatrixRotateZYX(Vector3 ang) +{ + Matrix result = { 0 }; + + float cz = cosf(ang.z); + float sz = sinf(ang.z); + float cy = cosf(ang.y); + float sy = sinf(ang.y); + float cx = cosf(ang.x); + float sx = sinf(ang.x); + + result.m0 = cz*cy; + result.m1 = cz*sy*sx - cx*sz; + result.m2 = sz*sx + cz*cx*sy; + result.m3 = 0; + + result.m4 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m6 = cx*sz*sy - cz*sx; + result.m7 = 0; + + result.m8 = -sy; + result.m9 = cy*sx; + result.m10 = cy*cx; + result.m11 = 0; + + result.m12 = 0; + result.m13 = 0; + result.m14 = 0; + result.m15 = 1; + + return result; +} + +// Returns scaling matrix +RMDEF Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Returns perspective projection matrix +RMDEF Matrix MatrixFrustum(double left, double right, double bottom, double top, double near, double far) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(far - near); + + result.m0 = ((float) near*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float) near*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)far + (float)near)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)far*(float)near*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Returns perspective projection matrix +// NOTE: Angle should be provided in radians +RMDEF Matrix MatrixPerspective(double fovy, double aspect, double near, double far) +{ + double top = near*tan(fovy*0.5); + double right = top*aspect; + Matrix result = MatrixFrustum(-right, right, -top, top, near, far); + + return result; +} + +// Returns orthographic projection matrix +RMDEF Matrix MatrixOrtho(double left, double right, double bottom, double top, double near, double far) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(far - near); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)far + (float)near)/fn; + result.m15 = 1.0f; + + return result; +} + +// Returns camera look-at matrix (view matrix) +RMDEF Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + Vector3 z = Vector3Subtract(eye, target); + z = Vector3Normalize(z); + Vector3 x = Vector3CrossProduct(up, z); + x = Vector3Normalize(x); + Vector3 y = Vector3CrossProduct(z, x); + + result.m0 = x.x; + result.m1 = y.x; + result.m2 = z.x; + result.m3 = 0.0f; + result.m4 = x.y; + result.m5 = y.y; + result.m6 = z.y; + result.m7 = 0.0f; + result.m8 = x.z; + result.m9 = y.z; + result.m10 = z.z; + result.m11 = 0.0f; + result.m12 = -Vector3DotProduct(x, eye); + result.m13 = -Vector3DotProduct(y, eye); + result.m14 = -Vector3DotProduct(z, eye); + result.m15 = 1.0f; + + return result; +} + +// Returns float array of matrix data +RMDEF float16 MatrixToFloatV(Matrix mat) +{ + float16 buffer = { 0 }; + + buffer.v[0] = mat.m0; + buffer.v[1] = mat.m1; + buffer.v[2] = mat.m2; + buffer.v[3] = mat.m3; + buffer.v[4] = mat.m4; + buffer.v[5] = mat.m5; + buffer.v[6] = mat.m6; + buffer.v[7] = mat.m7; + buffer.v[8] = mat.m8; + buffer.v[9] = mat.m9; + buffer.v[10] = mat.m10; + buffer.v[11] = mat.m11; + buffer.v[12] = mat.m12; + buffer.v[13] = mat.m13; + buffer.v[14] = mat.m14; + buffer.v[15] = mat.m15; + + return buffer; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMDEF Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + return result; +} + +// Add quaternion and float value +RMDEF Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + return result; +} + +// Subtract two quaternions +RMDEF Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + return result; +} + +// Subtract quaternion and float value +RMDEF Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + return result; +} + +// Returns identity quaternion +RMDEF Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + return result; +} + +// Computes the length of a quaternion +RMDEF float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + return result; +} + +// Normalize provided quaternion +RMDEF Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length, ilength; + length = QuaternionLength(q); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMDEF Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + float length = QuaternionLength(q); + float lengthSq = length*length; + + if (lengthSq != 0.0) + { + float i = 1.0f/lengthSq; + + result.x *= -i; + result.y *= -i; + result.z *= -i; + result.w *= i; + } + + return result; +} + +// Calculate two quaternion multiplication +RMDEF Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMDEF Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + float qax = q.x, qay = q.y, qaz = q.z, qaw = q.w; + + result.x = qax*mul + qaw*mul + qay*mul - qaz*mul; + result.y = qay*mul + qaw*mul + qaz*mul - qax*mul; + result.z = qaz*mul + qaw*mul + qax*mul - qay*mul; + result.w = qaw*mul - qax*mul - qay*mul - qaz*mul; + + return result; +} + +// Divide two quaternions +RMDEF Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + return result; +} + +// Calculate linear interpolation between two quaternions +RMDEF Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMDEF Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = QuaternionLerp(q1, q2, amount); + result = QuaternionNormalize(result); + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMDEF Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabs(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabs(sinHalfTheta) < 0.001f) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMDEF Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = Vector3DotProduct(from, to); + Vector3 cross = Vector3CrossProduct(from, to); + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = 1.0f + cos2Theta; // NOTE: Added QuaternioIdentity() + + // Normalize to essentially nlerp the original and identity to 0.5 + result = QuaternionNormalize(result); + + // Above lines are equivalent to: + //Quaternion result = QuaternionNlerp(q, QuaternionIdentity(), 0.5f); + + return result; +} + +// Returns a quaternion for a given rotation matrix +RMDEF Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + if ((mat.m0 > mat.m5) && (mat.m0 > mat.m10)) + { + float s = sqrtf(1.0f + mat.m0 - mat.m5 - mat.m10)*2; + + result.x = 0.25f*s; + result.y = (mat.m4 + mat.m1)/s; + result.z = (mat.m2 + mat.m8)/s; + result.w = (mat.m9 - mat.m6)/s; + } + else if (mat.m5 > mat.m10) + { + float s = sqrtf(1.0f + mat.m5 - mat.m0 - mat.m10)*2; + result.x = (mat.m4 + mat.m1)/s; + result.y = 0.25f*s; + result.z = (mat.m9 + mat.m6)/s; + result.w = (mat.m2 - mat.m8)/s; + } + else + { + float s = sqrtf(1.0f + mat.m10 - mat.m0 - mat.m5)*2; + result.x = (mat.m2 + mat.m8)/s; + result.y = (mat.m9 + mat.m6)/s; + result.z = 0.25f*s; + result.w = (mat.m4 - mat.m1)/s; + } + + return result; +} + +// Returns a matrix for a given quaternion +RMDEF Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = MatrixIdentity(); + + float a2 = 2*(q.x*q.x), b2=2*(q.y*q.y), c2=2*(q.z*q.z); //, d2=2*(q.w*q.w); + + float ab = 2*(q.x*q.y), ac=2*(q.x*q.z), bc=2*(q.y*q.z); + float ad = 2*(q.x*q.w), bd=2*(q.y*q.w), cd=2*(q.z*q.w); + + result.m0 = 1 - b2 - c2; + result.m1 = ab - cd; + result.m2 = ac + bd; + + result.m4 = ab + cd; + result.m5 = 1 - a2 - c2; + result.m6 = bc - ad; + + result.m8 = ac - bd; + result.m9 = bc + ad; + result.m10 = 1 - a2 - b2; + + return result; +} + +// Returns rotation quaternion for an angle and axis +// NOTE: angle must be provided in radians +RMDEF Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + if (Vector3Length(axis) != 0.0f) + + angle *= 0.5f; + + axis = Vector3Normalize(axis); + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + result = QuaternionNormalize(result); + + return result; +} + +// Returns the rotation angle and axis for a given quaternion +RMDEF void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabs(q.w) > 1.0f) q = QuaternionNormalize(q); + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > 0.0001f) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Returns the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMDEF Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion q = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + q.x = x1*y0*z0 - x0*y1*z1; + q.y = x0*y1*z0 + x1*y0*z1; + q.z = x0*y0*z1 - x1*y1*z0; + q.w = x0*y0*z0 + x1*y1*z1; + + return q; +} + +// Return the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in degrees +RMDEF Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1)*RAD2DEG; + + // pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0)*RAD2DEG; + + // yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1)*RAD2DEG; + + return result; +} + +// Transform a quaternion given a transformation matrix +RMDEF Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +// Projects a Vector3 from screen space into object space +RMDEF Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + // Calculate unproject matrix (multiply view patrix by projection matrix) and invert it + Matrix matViewProj = MatrixMultiply(view, projection); + matViewProj = MatrixInvert(matViewProj); + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unproject matrix + quat = QuaternionTransform(quat, matViewProj); + + // Normalized world points in vectors + result.x = quat.x/quat.w; + result.y = quat.y/quat.w; + result.z = quat.z/quat.w; + + return result; +} + +#endif // RAYMATH_H diff --git a/raylib/rglfw.c b/raylib/rglfw.c new file mode 100644 index 0000000..5728fa6 --- /dev/null +++ b/raylib/rglfw.c @@ -0,0 +1,120 @@ +/********************************************************************************************** +* +* rglfw - raylib GLFW single file compilation +* +* This file includes latest GLFW sources (https://github.com/glfw/glfw) to be compiled together +* with raylib for all supported platforms, this way, no external dependencies are required. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2017-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +//#define _GLFW_BUILD_DLL // To build shared version +//http://www.glfw.org/docs/latest/compile.html#compile_manual + +#if defined(_WIN32) + #define _GLFW_WIN32 +#endif +#if defined(__linux__) + #if !defined(_GLFW_WAYLAND) // Required for Wayland windowing + #define _GLFW_X11 + #endif +#endif +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + #define _GLFW_X11 +#endif +#if defined(__APPLE__) + #define _GLFW_COCOA + #define _GLFW_USE_MENUBAR // To create and populate the menu bar when the first window is created + #define _GLFW_USE_RETINA // To have windows use the full resolution of Retina displays +#endif +#if defined(__TINYC__) + #define _WIN32_WINNT_WINXP 0x0501 +#endif + +// NOTE: _GLFW_MIR experimental platform not supported at this moment + +#include "external/glfw/src/context.c" +#include "external/glfw/src/init.c" +#include "external/glfw/src/input.c" +#include "external/glfw/src/monitor.c" +#include "external/glfw/src/vulkan.c" +#include "external/glfw/src/window.c" + +#if defined(_WIN32) + #include "external/glfw/src/win32_init.c" + #include "external/glfw/src/win32_joystick.c" + #include "external/glfw/src/win32_monitor.c" + #include "external/glfw/src/win32_time.c" + #include "external/glfw/src/win32_thread.c" + #include "external/glfw/src/win32_window.c" + #include "external/glfw/src/wgl_context.c" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif + +#if defined(__linux__) + #if defined(_GLFW_WAYLAND) + #include "external/glfw/src/wl_init.c" + #include "external/glfw/src/wl_monitor.c" + #include "external/glfw/src/wl_window.c" + #include "external/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c" + #include "external/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c" + #endif + #if defined(_GLFW_X11) + #include "external/glfw/src/x11_init.c" + #include "external/glfw/src/x11_monitor.c" + #include "external/glfw/src/x11_window.c" + #include "external/glfw/src/glx_context.c" + #endif + + #include "external/glfw/src/linux_joystick.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/posix_time.c" + #include "external/glfw/src/xkb_unicode.c" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined( __NetBSD__) || defined(__DragonFly__) + #include "external/glfw/src/x11_init.c" + #include "external/glfw/src/x11_monitor.c" + #include "external/glfw/src/x11_window.c" + #include "external/glfw/src/xkb_unicode.c" + // TODO: Joystick implementation + #include "external/glfw/src/null_joystick.c" + #include "external/glfw/src/posix_time.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/glx_context.c" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif + +#if defined(__APPLE__) + #include "external/glfw/src/cocoa_init.m" + #include "external/glfw/src/cocoa_joystick.m" + #include "external/glfw/src/cocoa_monitor.m" + #include "external/glfw/src/cocoa_window.m" + #include "external/glfw/src/cocoa_time.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/nsgl_context.m" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif diff --git a/raylib/rlgl.h b/raylib/rlgl.h new file mode 100644 index 0000000..bb0bc23 --- /dev/null +++ b/raylib/rlgl.h @@ -0,0 +1,4076 @@ +/********************************************************************************************** +* +* rlgl v3.7 - raylib OpenGL abstraction layer +* +* rlgl is a wrapper for multiple OpenGL versions (1.1, 2.1, 3.3 Core, ES 2.0) to +* pseudo-OpenGL 1.1 style functions (rlVertex, rlTranslate, rlRotate...). +* +* When chosing an OpenGL version greater than OpenGL 1.1, rlgl stores vertex data on internal +* VBO buffers (and VAOs if available). It requires calling 3 functions: +* rlglInit() - Initialize internal buffers and auxiliary resources +* rlglClose() - De-initialize internal buffers data and other auxiliar resources +* +* CONFIGURATION: +* +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_ES2 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RLGL_STANDALONE +* Use rlgl as standalone library (no raylib dependency) +* +* #define SUPPORT_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* DEPENDENCIES: +* raymath - 3D math functionality (Vector3, Matrix, Quaternion) +* GLAD - OpenGL extensions loading (OpenGL 3.3 Core only) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLGL_H +#define RLGL_H + +#if defined(RLGL_STANDALONE) + #define RAYMATH_STANDALONE + #define RAYMATH_HEADER_ONLY + + #define RLAPI // We are building or using rlgl as a static library (or Linux shared library) + + #if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building raylib as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #endif + #endif + + // Support TRACELOG macros + #if !defined(TRACELOG) + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 + #endif + + // Allow custom memory allocators + #ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) + #endif + #ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) + #endif + #ifndef RL_REALLOC + #define RL_REALLOC(n,sz) realloc(n,sz) + #endif + #ifndef RL_FREE + #define RL_FREE(p) free(p) + #endif +#else + #include "raylib.h" // Required for: Shader, Texture2D +#endif + +#include "raymath.h" // Required for: Vector3, Matrix + +// Security check in case no GRAPHICS_API_OPENGL_* defined +#if !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_ES2) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Security check in case multiple GRAPHICS_API_OPENGL_* defined +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_21) + #undef GRAPHICS_API_OPENGL_21 + #endif + #if defined(GRAPHICS_API_OPENGL_33) + #undef GRAPHICS_API_OPENGL_33 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + #undef GRAPHICS_API_OPENGL_ES2 + #endif +#endif + +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 +#endif + +#define SUPPORT_RENDER_TEXTURES_HINT + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Default internal render batch limits +#ifndef DEFAULT_BATCH_BUFFER_ELEMENTS + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define DEFAULT_BATCH_BUFFER_ELEMENTS 8192 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + #define DEFAULT_BATCH_BUFFER_ELEMENTS 2048 + #endif +#endif +#ifndef DEFAULT_BATCH_BUFFERS + #define DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#endif +#ifndef DEFAULT_BATCH_DRAWCALLS + #define DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +#endif +#ifndef MAX_BATCH_ACTIVE_TEXTURES + #define MAX_BATCH_ACTIVE_TEXTURES 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) +#endif + +// Internal Matrix stack +#ifndef MAX_MATRIX_STACK_SIZE + #define MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack +#endif + +// Vertex buffers id limit +#ifndef MAX_MESH_VERTEX_BUFFERS + #define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#endif + +// Shader and material limits +#ifndef MAX_SHADER_LOCATIONS + #define MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#endif +#ifndef MAX_MATERIAL_MAPS + #define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported +#endif + +// Projection matrix culling +#ifndef RL_CULL_DISTANCE_NEAR + #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance +#endif +#ifndef RL_CULL_DISTANCE_FAR + #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance +#endif + +// Texture parameters (equivalent to OpenGL defines) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER + +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) + +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef enum { OPENGL_11 = 1, OPENGL_21, OPENGL_33, OPENGL_ES_20 } GlVersion; + +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, + RL_ATTACHMENT_COLOR_CHANNEL1, + RL_ATTACHMENT_COLOR_CHANNEL2, + RL_ATTACHMENT_COLOR_CHANNEL3, + RL_ATTACHMENT_COLOR_CHANNEL4, + RL_ATTACHMENT_COLOR_CHANNEL5, + RL_ATTACHMENT_COLOR_CHANNEL6, + RL_ATTACHMENT_COLOR_CHANNEL7, + RL_ATTACHMENT_DEPTH = 100, + RL_ATTACHMENT_STENCIL = 200, +} FramebufferAttachType; + +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z, + RL_ATTACHMENT_TEXTURE2D = 100, + RL_ATTACHMENT_RENDERBUFFER = 200, +} FramebufferAttachTextureType; + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct VertexBuffer { + int elementsCount; // Number of elements in the buffer (QUADS) + + int vCounter; // Vertex position counter to process (and draw) from full buffer + int tcCounter; // Vertex texcoord counter to process (and draw) from full buffer + int cCounter; // Vertex color counter to process (and draw) from full buffer + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) +} VertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct DrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShader.id + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} DrawCall; + +// RenderBatch type +typedef struct RenderBatch { + int buffersCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + VertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + DrawCall *draws; // Draw calls array, depends on textureId + int drawsCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} RenderBatch; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, + SHADER_ATTRIB_VEC2, + SHADER_ATTRIB_VEC3, + SHADER_ATTRIB_VEC4 +} ShaderAttributeDataType; + +#if defined(RLGL_STANDALONE) + #ifndef __cplusplus + // Boolean type + typedef enum { false, true } bool; + #endif + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Texture type + // NOTE: Data stored in GPU memory + typedef struct Texture2D { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat) + } Texture2D; + + // Shader type (generic) + typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (MAX_SHADER_LOCATIONS) + } Shader; + + // TraceLog message types + typedef enum { + LOG_ALL, + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE + } TraceLogLevel; + + // Texture formats (support depends on OpenGL version) + typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_RGB565_BE, // 16 bpp (big endian) + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp + } PixelFormat; + + // Texture parameters: filter mode + // NOTE 1: Filtering considers mipmaps if available in the texture + // NOTE 2: Filter is accordingly set for minification and magnification + typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel aproximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x + } TextureFilter; + + // Texture parameters: wrap mode + typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode + } TextureWrap; + + // Color blending modes (pre-defined) + typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_CUSTOM // Belnd textures using custom src/dst factors (use SetBlendModeCustom()) + } BlendMode; + + // Shader location point type + typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, + SHADER_LOC_VERTEX_TEXCOORD01, + SHADER_LOC_VERTEX_TEXCOORD02, + SHADER_LOC_VERTEX_NORMAL, + SHADER_LOC_VERTEX_TANGENT, + SHADER_LOC_VERTEX_COLOR, + SHADER_LOC_MATRIX_MVP, + SHADER_LOC_MATRIX_MODEL, + SHADER_LOC_MATRIX_VIEW, + SHADER_LOC_MATRIX_NORMAL, + SHADER_LOC_MATRIX_PROJECTION, + SHADER_LOC_VECTOR_VIEW, + SHADER_LOC_COLOR_DIFFUSE, + SHADER_LOC_COLOR_SPECULAR, + SHADER_LOC_COLOR_AMBIENT, + SHADER_LOC_MAP_ALBEDO, // SHADER_LOC_MAP_DIFFUSE + SHADER_LOC_MAP_METALNESS, // SHADER_LOC_MAP_SPECULAR + SHADER_LOC_MAP_NORMAL, + SHADER_LOC_MAP_ROUGHNESS, + SHADER_LOC_MAP_OCCLUSION, + SHADER_LOC_MAP_EMISSION, + SHADER_LOC_MAP_HEIGHT, + SHADER_LOC_MAP_CUBEMAP, + SHADER_LOC_MAP_IRRADIANCE, + SHADER_LOC_MAP_PREFILTER, + SHADER_LOC_MAP_BRDF + } ShaderLocationIndex; + + #define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO + #define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + + // Shader uniform data types + typedef enum { + SHADER_UNIFORM_FLOAT = 0, + SHADER_UNIFORM_VEC2, + SHADER_UNIFORM_VEC3, + SHADER_UNIFORM_VEC4, + SHADER_UNIFORM_INT, + SHADER_UNIFORM_IVEC2, + SHADER_UNIFORM_IVEC3, + SHADER_UNIFORM_IVEC4, + SHADER_UNIFORM_SAMPLER2D + } ShaderUniformDataType; +#endif + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//------------------------------------------------------------------------------------ +// Functions Declaration - Matrix operations +//------------------------------------------------------------------------------------ +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop lattest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angleDeg, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(float *matf); // Multiply the current matrix by another matrix +RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area + +//------------------------------------------------------------------------------------ +// Functions Declaration - Vertex level operations +//------------------------------------------------------------------------------------ +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +//------------------------------------------------------------------------------------ +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom +//------------------------------------------------------------------------------------ + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id);// Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index);// Disable vertex attribute index +#if defined(GRAPHICS_API_OPENGL_11) +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); +RLAPI void rlDisableStatePointer(int vertexAttribType); +#endif + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap +RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer + +// General render state +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlDisableWireMode(void); // Disable wire mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) + +//------------------------------------------------------------------------------------ +// Functions Declaration - rlgl functionality +//------------------------------------------------------------------------------------ +// rlgl initialization functions +RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) +RLAPI void rlglClose(void); // De-inititialize rlgl (buffers, shaders, textures) +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) +RLAPI int rlGetVersion(void); // Returns current OpenGL version +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height + +RLAPI Shader rlGetShaderDefault(void); // Get default shader +RLAPI Texture2D rlGetTextureDefault(void); // Get default texture + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI RenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(RenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(RenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(RenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic); // Load a vertex buffer attribute +RLAPI unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic); // Load a new attributes element buffer +RLAPI void rlUpdateVertexBuffer(int bufferId, void *data, int dataSize, int offset); // Update GPU buffer with new data +RLAPI void rlUnloadVertexArray(unsigned int vaoId); +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer); +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value +RLAPI void rlDrawVertexArray(int offset, int count); +RLAPI void rlDrawVertexArrayElements(int offset, int count, void *buffer); +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances); + +// Textures management +RLAPI unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(void *data, int size, int format); // Load texture cubemap +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update GPU texture with new data +RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats +RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +RLAPI void rlGenerateMipmaps(Texture2D *texture); // Generate mipmap data for selected texture +RLAPI void *rlReadTexturePixels(Texture2D texture); // Read texture pixel data +RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) + +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(int width, int height); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU + +// Shaders management +RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: GL_VERTEX_SHADER, GL_FRAGMENT_SHADER) +RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform +RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(Shader shader); // Set shader currently active + +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering + +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad +#if defined(__cplusplus) +} +#endif + +#endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +#if !defined(RLGL_STANDALONE) + // Check if config flags have been externally provided on compilation line + #if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags + #endif + #include "raymath.h" // Required for: Vector3 and Matrix functions +#endif + +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(__APPLE__) + #include // OpenGL 1.1 library for OSX + #include + #else + // APIENTRY for OpenGL function pointer declarations is required + #ifndef APIENTRY + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include // OpenGL 1.1 library + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #if defined(__APPLE__) + #include // OpenGL 3 library for OSX + #include // OpenGL 3 extensions library for OSX + #else + #define GLAD_REALLOC RL_REALLOC + #define GLAD_FREE RL_FREE + + #define GLAD_IMPLEMENTATION + #if defined(RLGL_STANDALONE) + #include "glad.h" // GLAD extensions loading library, includes OpenGL headers + #else + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers + #endif + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define GL_GLEXT_PROTOTYPES + //#include // EGL library -> not required, platform layer + #include // OpenGL ES 2.0 library + #include // OpenGL ES 2.0 extensions library + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + //#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define glClearDepth glClearDepthf + #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER +#endif + +// Default shader vertex attribute names to set location points +#ifndef DEFAULT_SHADER_ATTRIB_NAME_POSITION + #define DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Binded by default to shader location: 0 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD + #define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Binded by default to shader location: 1 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_NORMAL + #define DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Binded by default to shader location: 2 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_COLOR + #define DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Binded by default to shader location: 3 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_TANGENT + #define DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Binded by default to shader location: 4 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 + #define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Binded by default to shader location: 5 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +typedef struct rlglData { + RenderBatch *currentBatch; // Current render batch + RenderBatch defaultBatch; // Default internal render batch + + struct { + int currentMatrixMode; // Current matrix mode + Matrix *currentMatrix; // Current matrix pointer + Matrix modelview; // Default modelview matrix + Matrix projection; // Default projection matrix + Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale + bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) + Matrix stack[MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop + int stackCounter; // Matrix stack counter + + unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[MAX_BATCH_ACTIVE_TEXTURES]; // Active texture ids to be enabled on batch drawing (0 active by default) + unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) + unsigned int defaultFShaderId; // Default fragment shader Id (used by default shader program) + Shader defaultShader; // Basic shader, support vertex color and diffuse texture + Shader currentShader; // Shader to be used on rendering (by default, defaultShader) + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + + int currentBlendMode; // Blending mode active + int glBlendSrcFactor; // Blending source factor + int glBlendDstFactor; // Blending destination factor + int glBlendEquation; // Blending equation + + int framebufferWidth; // Default framebuffer width + int framebufferHeight; // Default framebuffer height + + } State; // Renderer state + struct { + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_WEBGL_depth_texture, GL_OES_depth_texture) + bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) + bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) + bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) + + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) + int maxDepthBits; // Maximum bits for depth component + + } ExtSupported; // Extensions supported flags +} rlglData; + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static rlglData RLGL = { 0 }; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_ES2) +// NOTE: VAO functionality is exposed through extensions (OES) +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// NOTE: Instancing functionality could also be available through extension +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static void rlLoadShaderDefault(void); // Load default shader (RLGL.State.defaultShader) +static void rlUnloadShaderDefault(void); // Unload default shader (RLGL.State.defaultShader) +#if defined(SUPPORT_GL_DETAILS_INFO) +static char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif // SUPPORT_GL_DETAILS_INFO +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +#if defined(GRAPHICS_API_OPENGL_11) +static int rlGenerateMipmapsData(unsigned char *data, int baseWidth, int baseHeight); // Generate mipmaps data on CPU side +static Color *rlGenNextMipmapData(Color *srcData, int srcWidth, int srcHeight); // Generate next mipmap level on CPU side +#endif +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix operations +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlMatrixMode(int mode) +{ + switch (mode) + { + case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; + case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; + case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; + default: break; + } +} + +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + glFrustum(left, right, bottom, top, znear, zfar); +} + +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + glOrtho(left, right, bottom, top, znear, zfar); +} + +void rlPushMatrix(void) { glPushMatrix(); } +void rlPopMatrix(void) { glPopMatrix(); } +void rlLoadIdentity(void) { glLoadIdentity(); } +void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } +void rlRotatef(float angleDeg, float x, float y, float z) { glRotatef(angleDeg, x, y, z); } +void rlScalef(float x, float y, float z) { glScalef(x, y, z); } +void rlMultMatrixf(float *matf) { glMultMatrixf(matf); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Choose the current matrix to be transformed +void rlMatrixMode(int mode) +{ + if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; + else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; + //else if (mode == RL_TEXTURE) // Not supported + + RLGL.State.currentMatrixMode = mode; +} + +// Push the current matrix into RLGL.State.stack +void rlPushMatrix(void) +{ + if (RLGL.State.stackCounter >= MAX_MATRIX_STACK_SIZE) TRACELOG(LOG_ERROR, "RLGL: Matrix stack overflow (MAX_MATRIX_STACK_SIZE)"); + + if (RLGL.State.currentMatrixMode == RL_MODELVIEW) + { + RLGL.State.transformRequired = true; + RLGL.State.currentMatrix = &RLGL.State.transform; + } + + RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; + RLGL.State.stackCounter++; +} + +// Pop lattest inserted matrix from RLGL.State.stack +void rlPopMatrix(void) +{ + if (RLGL.State.stackCounter > 0) + { + Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; + *RLGL.State.currentMatrix = mat; + RLGL.State.stackCounter--; + } + + if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) + { + RLGL.State.currentMatrix = &RLGL.State.modelview; + RLGL.State.transformRequired = false; + } +} + +// Reset current matrix to identity matrix +void rlLoadIdentity(void) +{ + *RLGL.State.currentMatrix = MatrixIdentity(); +} + +// Multiply the current matrix by a translation matrix +void rlTranslatef(float x, float y, float z) +{ + Matrix matTranslation = MatrixTranslate(x, y, z); + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = MatrixMultiply(matTranslation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a rotation matrix +void rlRotatef(float angleDeg, float x, float y, float z) +{ + Matrix matRotation = MatrixIdentity(); + + Vector3 axis = (Vector3){ x, y, z }; + matRotation = MatrixRotate(Vector3Normalize(axis), angleDeg*DEG2RAD); + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = MatrixMultiply(matRotation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = MatrixScale(x, y, z); + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = MatrixMultiply(matScale, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(float *matf) +{ + // Matrix creation from array + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *RLGL.State.currentMatrix = MatrixMultiply(*RLGL.State.currentMatrix, mat); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + Matrix matPerps = MatrixFrustum(left, right, bottom, top, znear, zfar); + + *RLGL.State.currentMatrix = MatrixMultiply(*RLGL.State.currentMatrix, matPerps); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + // NOTE: If left-right and top-botton values are equal it could create + // a division by zero on MatrixOrtho(), response to it is platform/compiler dependant + Matrix matOrtho = MatrixOrtho(left, right, bottom, top, znear, zfar); + + *RLGL.State.currentMatrix = MatrixMultiply(*RLGL.State.currentMatrix, matOrtho); +} +#endif + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +void rlViewport(int x, int y, int width, int height) +{ + glViewport(x, y, width, height); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vertex level operations +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlBegin(int mode) +{ + switch (mode) + { + case RL_LINES: glBegin(GL_LINES); break; + case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; + case RL_QUADS: glBegin(GL_QUADS); break; + default: break; + } +} + +void rlEnd() { glEnd(); } +void rlVertex2i(int x, int y) { glVertex2i(x, y); } +void rlVertex2f(float x, float y) { glVertex2f(x, y); } +void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } +void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } +void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } +void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } +void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Initialize drawing mode (how to organize vertex) +void rlBegin(int mode) +{ + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode != mode) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawsCounter++; + } + } + + if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode = mode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount = 0; + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId = RLGL.State.defaultTextureId; + } +} + +// Finish vertex providing +void rlEnd(void) +{ + // Make sure vertexCount is the same for vertices, texcoords, colors and normals + // NOTE: In OpenGL 1.1, one glColor call can be made for all the subsequent glVertex calls + + // Make sure colors count match vertex count + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter != RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter) + { + int addColors = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter; + + for (int i = 0; i < addColors; i++) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 4]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 1] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 3]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 2] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 2]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 3] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 1]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter++; + } + } + + // Make sure texcoords count match vertex count + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter != RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter) + { + int addTexCoords = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter; + + for (int i = 0; i < addTexCoords; i++) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter] = 0.0f; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter + 1] = 0.0f; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter++; + } + } + + // TODO: Make sure normals count match vertex count... if normals support is added in a future... :P + + // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + RLGL.currentBatch->currentDepth += (1.0f/20000.0f); + + // Verify internal buffers limits + // NOTE: This check is combined with usage of rlCheckRenderBatchLimit() + if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4 - 4)) + { + // WARNING: If we are between rlPushMatrix() and rlPopMatrix() and we need to force a rlDrawRenderBatch(), + // we need to call rlPopMatrix() before to recover *RLGL.State.currentMatrix (RLGL.State.modelview) for the next forced draw call! + // If we have multiple matrix pushed, it will require "RLGL.State.stackCounter" pops before launching the draw + for (int i = RLGL.State.stackCounter; i >= 0; i--) rlPopMatrix(); + rlDrawRenderBatch(RLGL.currentBatch); + } +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +void rlVertex3f(float x, float y, float z) +{ + Vector3 vec = { x, y, z }; + + // Transform provided vector if required + if (RLGL.State.transformRequired) vec = Vector3Transform(vec, RLGL.State.transform); + + // Verify that current vertex buffer elements limit has not been reached + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter < (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4)) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter] = vec.x; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + 1] = vec.y; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + 2] = vec.z; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter++; + + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount++; + } + else TRACELOG(LOG_ERROR, "RLGL: Batch elements overflow"); +} + +// Define one vertex (position) +void rlVertex2f(float x, float y) +{ + rlVertex3f(x, y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (position) +void rlVertex2i(int x, int y) +{ + rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (texture coordinate) +// NOTE: Texture coordinates are limited to QUADS only +void rlTexCoord2f(float x, float y) +{ + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter] = x; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter + 1] = y; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter++; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +void rlNormal3f(float x, float y, float z) +{ + // TODO: Normals usage... +} + +// Define one vertex (color) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter] = x; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 1] = y; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 2] = z; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 3] = w; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter++; +} + +// Define one vertex (color) +void rlColor4f(float r, float g, float b, float a) +{ + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Define one vertex (color) +void rlColor3f(float x, float y, float z) +{ + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +#endif + +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- + +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawsCounter++; + } + } + + if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture +void rlEnableTexture(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, id); +} + +// Disable texture +void rlDisableTexture(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnable(GL_TEXTURE_CUBE_MAP); // Core in OpenGL 1.4 + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisable(GL_TEXTURE_CUBE_MAP); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); + else TRACELOG(LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); +#endif + } + else glTexParameteri(GL_TEXTURE_2D, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(LOG_WARNING, "GL: Anisotropic filtering not supported"); +#endif + } break; + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + +// Enable rendering to texture (fbo) +void rlEnableFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); +#endif +} + +// Disable rendering to texture +void rlDisableFramebuffer(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Enable depth test +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } + +// Disable depth test +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Enable scissor test +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } + +// Scissor test +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } + +// Enable wire mode +void rlEnableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} +// Set the line drawing width +void rlSetLineWidth(float width) +{ + glLineWidth(width); +} + +// Get the line drawing width +float rlGetLineWidth(void) +{ + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} + +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); +#endif +} + +// Disable line aliasing +void rlDisableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); +#endif +} + +// Enable stereo rendering +void rlEnableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; +#endif +} + +// Disable stereo rendering +void rlDisableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; +#endif +} + +// Clear color buffer with color +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // Color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + glClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +void rlClearScreenBuffers(void) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Check and log OpenGL error codes +void rlCheckErrors() +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentBlendMode != mode) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case BLEND_CUSTOM: glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - rlgl functionality +//---------------------------------------------------------------------------------- + +// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states +void rlglInit(int width, int height) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + + if (RLGL.State.defaultTextureId != 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load default texture"); + + // Init default Shader (customized for GL 3.3 and ES2) + rlLoadShaderDefault(); // RLGL.State.defaultShader + RLGL.State.currentShader = RLGL.State.defaultShader; + + // Init default vertex arrays buffers + RLGL.defaultBatch = rlLoadRenderBatch(DEFAULT_BATCH_BUFFERS, DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.currentBatch = &RLGL.defaultBatch; + + // Init stack matrices (emulating OpenGL 1.1) + for (int i = 0; i < MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = MatrixIdentity(); + + // Init internal matrices + RLGL.State.transform = MatrixIdentity(); + RLGL.State.projection = MatrixIdentity(); + RLGL.State.modelview = MatrixIdentity(); + RLGL.State.currentMatrix = &RLGL.State.modelview; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + + // Initialize OpenGL default states + //---------------------------------------------------------- + // Init state: Depth test + glDepthFunc(GL_LEQUAL); // Type of depth testing to apply + glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) + glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + glCullFace(GL_BACK); // Cull the back face (default) + glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) + glEnable(GL_CULL_FACE); // Enable backface culling + + // Init state: Cubemap seamless +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + // Init state: Color hints (deprecated in OpenGL 3.0+) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation + glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Store screen size into global variables + RLGL.State.framebufferWidth = width; + RLGL.State.framebufferHeight = height; + + TRACELOG(LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +#endif + + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) +} + +// Vertex Buffer Object deinitialization (memory free) +void rlglClose(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader + + glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); +#endif +} + +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + #if !defined(__APPLE__) + if (!gladLoadGLLoader((GLADloadproc)loader)) TRACELOG(LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + #endif + + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(SUPPORT_GL_DETAILS_INFO) + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + char **extList = RL_MALLOC(sizeof(char *)*numExt); + TRACELOG(LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) + { + extList[i] = (char *)glGetStringi(GL_EXTENSIONS, i); + TRACELOG(LOG_INFO, " %s", extList[i]); + } + RL_FREE(extList); // Free extensions pointers +#endif + + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + #if !defined(__APPLE__) + // NOTE: With GLAD, we can check if an extension is supported using the GLAD_GL_xxx booleans + if (GLAD_GL_EXT_texture_compression_s3tc) RLGL.ExtSupported.texCompDXT = true; // Texture compression: DXT + if (GLAD_GL_ARB_ES3_compatibility) RLGL.ExtSupported.texCompETC2 = true; // Texture compression: ETC2/EAC + #endif +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES2) + // Get supported extensions list + GLint numExt = 0; + const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: We have to duplicate string because glGetString() returns a const string + int len = strlen(extensions) + 1; + char *extensionsDup = (char *)RL_CALLOC(len, sizeof(char)); + strcpy(extensionsDup, extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < len; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } + } + + TRACELOG(LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(SUPPORT_GL_DETAILS_INFO) + TRACELOG(LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(LOG_INFO, " %s", extList[i]); +#endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // Web ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else + { + if ((strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) && // Standard EXT + (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0)) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + + // Check depth texture support + if ((strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0)) RLGL.ExtSupported.texDepth = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if defined(SUPPORT_GL_DETAILS_INFO) + // Show some OpenGL GPU capabilities + TRACELOG(LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint format[32] = { 0 }; + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, format); + for (int i = 0; i < capability; i++) TRACELOG(LOG_INFO, " %s", rlGetCompressedFormatName(format[i])); + + /* + // Following capabilities are only supported by OpenGL 4.3 or greater + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); + */ +#else // SUPPORT_GL_DETAILS_INFO + + // Show some basic info about GL supported features + #if defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) TRACELOG(LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + #endif + if (RLGL.ExtSupported.texCompDXT) TRACELOG(LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(LOG_INFO, "GL: ASTC compressed textures supported"); +#endif // SUPPORT_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +} + +// Returns current OpenGL version +int rlGetVersion(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + return OPENGL_11; +#endif +#if defined(GRAPHICS_API_OPENGL_21) + #if defined(__APPLE__) + return OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX + #else + return OPENGL_21; + #endif +#elif defined(GRAPHICS_API_OPENGL_33) + return OPENGL_33; +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + return OPENGL_ES_20; +#endif +} + +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.framebufferWidth; +#else + return 0; +#endif +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.framebufferHeight; +#else + return 0; +#endif +} + +// Get default internal shader (simple texture + tint color) +Shader rlGetShaderDefault(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.defaultShader; +#else + Shader shader = { 0 }; + return shader; +#endif +} + +// Get default internal texture (white texture) +Texture2D rlGetTextureDefault(void) +{ + Texture2D texture = { 0 }; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + texture.id = RLGL.State.defaultTextureId; + texture.width = 1; + texture.height = 1; + texture.mipmaps = 1; + texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; +#endif + return texture; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +RenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + RenderBatch batch = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (VertexBuffer *)RL_MALLOC(sizeof(VertexBuffer)*numBuffers); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementsCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + batch.vertexBuffer[i].vCounter = 0; + batch.vertexBuffer[i].tcCounter = 0; + batch.vertexBuffer[i].cCounter = 0; + } + + TRACELOG(LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (DrawCall *)RL_MALLOC(DEFAULT_BATCH_DRAWCALLS*sizeof(DrawCall)); + + for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = MatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = MatrixIdentity(); + } + + batch.buffersCount = numBuffers; // Record buffer count + batch.drawsCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(RenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + glDisableVertexAttribArray(3); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.buffersCount; i++) + { + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +void rlDrawRenderBatch(RenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) + if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue. + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job. + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer(). + // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data. + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (batch->vertexBuffer[batch->currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyesCount = 1; + if (RLGL.State.stereoRender) eyesCount = 2; + + for (int eye = 0; eye < eyesCount; eye++) + { + if (eyesCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(MatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShader.id); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = MatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + glUniformMatrix4fv(RLGL.State.currentShader.locs[SHADER_LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShader.locs[SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShader.locs[SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawsCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and binded to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { +#if defined(GRAPHICS_API_OPENGL_33) + // We need to define the number of indices to be processed: quadsCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); +#endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counters for next frame + batch->vertexBuffer[batch->currentBuffer].vCounter = 0; + batch->vertexBuffer[batch->currentBuffer].tcCounter = 0; + batch->vertexBuffer[batch->currentBuffer].cCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawsCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->buffersCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(RenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + +// Check internal buffer overflow for a given number of vertex +// and force a RenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) +{ + bool overflow = false; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4)) + { + overflow = true; + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + } +#endif + + return overflow; +} + +// Textures data management +//----------------------------------------------------------------------------------------- +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount) +{ + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + unsigned int id = 0; + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) +#if defined(GRAPHICS_API_OPENGL_11) + if (format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#else + if ((!RLGL.ExtSupported.texCompDXT) && ((format == PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == PIXELFORMAT_COMPRESSED_DXT5_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!RLGL.ExtSupported.texCompETC1) && (format == PIXELFORMAT_COMPRESSED_ETC1_RGB)) + { + TRACELOG(LOG_WARNING, "GL: ETC1 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompETC2) && ((format == PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: ETC2 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == PIXELFORMAT_COMPRESSED_PVRT_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: PVRT compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompASTC) && ((format == PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: ASTC compressed texture format not supported"); + return id; + } +#endif +#endif // GRAPHICS_API_OPENGL_11 + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &id); // Generate texture id + + glBindTexture(GL_TEXTURE_2D, id); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != -1) + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, (unsigned char *)data + mipOffset); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, (unsigned char *)data + mipOffset); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if (RLGL.ExtSupported.texNPOT) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if (mipmapCount > 1) + { + // Activate Trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } +#endif + + // At this point we have the texture loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + glBindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i - %i mipmaps)", id, width, height, mipmapCount); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load texture"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture/WEBGL_depth_texture extensions +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // In case depth textures not supported, we force renderbuffer usage + if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; + + // NOTE: We let the implementation to choose the best bit-depth + // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F + unsigned int glInternalFormat = GL_DEPTH_COMPONENT; + +#if defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; + else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; + else glInternalFormat = GL_DEPTH_COMPONENT16; +#endif + + if (!useRenderBuffer && RLGL.ExtSupported.texDepth) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(LOG_INFO, "TEXTURE: Depth texture loaded successfully"); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); + } +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +unsigned int rlLoadTextureCubemap(void *data, int size, int format) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int dataSize = rlGetPixelDataSize(size, size, format); + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != -1) + { + // Load cubemap faces + for (unsigned int i = 0; i < 6; i++) + { + if (data == NULL) + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if (format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) + { + // Instead of using a sized internal texture format (GL_RGB16F, GL_RGB32F), we let the driver to choose the better format for us (GL_RGB) + if (RLGL.ExtSupported.texFloat32) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); + else TRACELOG(LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + } + else if ((format == PIXELFORMAT_UNCOMPRESSED_R32) || (format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, NULL); + } + else TRACELOG(LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, (unsigned char *)data + i*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); + } + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + } + + // Set cubemap texture sampling parameters + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + if (id > 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; +} + +// Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... +void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != -1) && (format < PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, (unsigned char *)data); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); +} + +// Get OpenGL internal formats and data type from raylib PixelFormat +void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) +{ + *glInternalFormat = -1; + *glFormat = -1; + *glType = -1; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case PIXELFORMAT_UNCOMPRESSED_RGB565_BE: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5_REV; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + case PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TRACELOG(LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; + } +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + glDeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +void rlGenerateMipmaps(Texture2D *texture) +{ + glBindTexture(GL_TEXTURE_2D, texture->id); + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((texture->width > 0) && ((texture->width & (texture->width - 1)) == 0)) && + ((texture->height > 0) && ((texture->height & (texture->height - 1)) == 0))) texIsPOT = true; + +#if defined(GRAPHICS_API_OPENGL_11) + if (texIsPOT) + { + // WARNING: Manual mipmap generation only works for RGBA 32bit textures! + if (texture->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + // Retrieve texture data from VRAM + void *texData = rlReadTexturePixels(*texture); + + // NOTE: Texture data size is reallocated to fit mipmaps data + // NOTE: CPU mipmap generation only supports RGBA 32bit data + int mipmapCount = rlGenerateMipmapsData(texData, texture->width, texture->height); + + int size = texture->width*texture->height*4; + int offset = size; + + int mipWidth = texture->width/2; + int mipHeight = texture->height/2; + + // Load the mipmaps + for (int level = 1; level < mipmapCount; level++) + { + glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)texData + offset); + + size = mipWidth*mipHeight*4; + offset += size; + + mipWidth /= 2; + mipHeight /= 2; + } + + texture->mipmaps = mipmapCount + 1; + RL_FREE(texData); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data + + TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Mipmaps generated manually on CPU side, total: %i", texture->id, texture->mipmaps); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps for provided texture format", texture->id); + } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps + + #define MIN(a,b) (((a)<(b))?(a):(b)) + #define MAX(a,b) (((a)>(b))?(a):(b)) + + texture->mipmaps = 1 + (int)floor(log(MAX(texture->width, texture->height))/log(2)); + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", texture->id, texture->mipmaps); + } +#endif + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", texture->id); + + glBindTexture(GL_TEXTURE_2D, 0); +} + + +// Read texture pixel data +void *rlReadTexturePixels(Texture2D texture) +{ + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, texture.id); + + // NOTE: Using texture.id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding. + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting. + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(texture.format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(texture.width, texture.height, texture.format); + + if ((glInternalFormat != -1) && (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + pixels = RL_MALLOC(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", texture.id, texture.format); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0. There is no way to get it from texture id. + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // We are using Option 1, just need to care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(texture.width, texture.height); + + // TODO: Create depth texture/renderbuffer for fbo? + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0); + + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(texture.width, texture.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); + glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); +#endif + + return pixels; +} + + +// Read screen pixel data (color buffer) +unsigned char *rlReadScreenPixels(int width, int height) +{ + unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); + + // Flip image vertically! + unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); + + for (int y = height - 1; y >= 0; y--) + { + for (int x = 0; x < (width*4); x++) + { + imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line + + // Set alpha component value to 255 (no trasparent image retrieval) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; + } + } + + RL_FREE(screenData); + + return imgData; // NOTE: image data should be freed +} + +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(int width, int height) +{ + unsigned int fboId = 0; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer +#endif + + return fboId; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0, depthId = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_RENDERBUFFER) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer. + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update GPU buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(int bufferId, void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, bufferId); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +void rlDrawVertexArrayElements(int offset, int count, void *buffer) +{ + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short*)buffer + offset); +} + +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); +#endif +} + +void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short*)buffer + offset, instances); +#endif +} + +#if defined(GRAPHICS_API_OPENGL_11) +void rlEnableStatePointer(int vertexAttribType, void *buffer) +{ + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } +} + +void rlDisableStatePointer(int vertexAttribType) +{ + glDisableClientState(vertexAttribType); +} +#endif + +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenVertexArrays(1, &vaoId); +#endif + return vaoId; +} + +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribPointer(index, compSize, type, normalized, stride, pointer); +#endif +} + +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribDivisor(index, divisor); +#endif +} + +void rlUnloadVertexArray(unsigned int vaoId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif +} + +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- +// Load shader from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = RLGL.State.defaultVShaderId; + unsigned int fragmentShaderId = RLGL.State.defaultFShaderId; + + if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShader.id; + else + { + id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); + + if (vertexShaderId != RLGL.State.defaultVShaderId) + { + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); + } + + if (id == 0) + { + TRACELOG(LOG_WARNING, "SHADER: Failed to load custom shader code"); + id = RLGL.State.defaultShader.id; + } + } + + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256]; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } +#endif + + return id; +} + +// Compile custom shader and return shader id +unsigned int rlCompileShader(const char *shaderCode, int type) +{ + unsigned int shader = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderCode, NULL); + + GLint success = 0; + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success == GL_FALSE) + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; + //case GL_GEOMETRY_SHADER: + //case GL_COMPUTE_SHADER: + default: break; + } + + int maxLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shader, maxLength, &length, log); + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); + RL_FREE(log); + } + } + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; + //case GL_GEOMETRY_SHADER: + //case GL_COMPUTE_SHADER: + default: break; + } + } +#endif + + return shader; +} + +// Load custom shader strings and return program id +unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLint success = 0; + program = glCreateProgram(); + + glAttachShader(program, vShaderId); + glAttachShader(program, fShaderId); + + // NOTE: Default attribute shader locations must be binded before linking + glBindAttribLocation(program, 0, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(program, 1, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(program, 2, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(program, 3, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(program, 4, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(program, 5, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); +#endif + return program; +} + +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + + TRACELOG(LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shaderId, uniformName); + + if (location == -1) TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(shaderId, attribName); + + if (location == -1) TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUniformMatrix4fv(locIndex, 1, false, MatrixToFloat(mat)); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) if (RLGL.State.activeTextureId[i] == textureId) return; + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active +void rlSetShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShader.id != shader.id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShader = shader; + } +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Return internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; matrix.m1 = mat[1]; matrix.m2 = mat[2]; matrix.m3 = mat[3]; + matrix.m4 = mat[4]; matrix.m5 = mat[5]; matrix.m6 = mat[6]; matrix.m7 = mat[7]; + matrix.m8 = mat[8]; matrix.m9 = mat[9]; matrix.m10 = mat[10]; matrix.m11 = mat[11]; + matrix.m12 = mat[12]; matrix.m13 = mat[13]; matrix.m14 = mat[14]; matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Return internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; m.m1 = mat[1]; m.m2 = mat[2]; m.m3 = mat[3]; + m.m4 = mat[4]; m.m5 = mat[5]; m.m6 = mat[6]; m.m7 = mat[7]; + m.m8 = mat[8]; m.m9 = mat[9]; m.m10 = mat[10]; m.m11 = mat[11]; + m.m12 = mat[12]; m.m13 = mat[13]; m.m14 = mat[14]; m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //Matrix matStackTransform = MatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = MatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a 1x1 XY quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a 1x1 3D cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load default shader (just vertex positioning and texture coloring) +// NOTE: This shader program is used for internal buffers +// NOTE: It uses global variable: RLGL.State.defaultShader +static void rlLoadShaderDefault(void) +{ + RLGL.State.defaultShader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShader.locs[i] = -1; + + // Vertex shader directly defined, no external file required + const char *vShaderDefault = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#endif + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + const char *fShaderDefault = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + + // NOTE: Compiled vertex/fragment shaders are kept for re-use + RLGL.State.defaultVShaderId = rlCompileShader(vShaderDefault, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlCompileShader(fShaderDefault, GL_FRAGMENT_SHADER); // Compile default fragment shader + + RLGL.State.defaultShader.id = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + + if (RLGL.State.defaultShader.id > 0) + { + TRACELOG(LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShader.id); + + // Set default shader locations: attributes locations + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexPosition"); + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexTexCoord"); + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexColor"); + + // Set default shader locations: uniform locations + RLGL.State.defaultShader.locs[SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShader.id, "mvp"); + RLGL.State.defaultShader.locs[SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShader.id, "colDiffuse"); + RLGL.State.defaultShader.locs[SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShader.id, "texture0"); + } + else TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShader.id); +} + +// Unload default shader +// NOTE: It uses global variable: RLGL.State.defaultShader +static void rlUnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(RLGL.State.defaultShader.id, RLGL.State.defaultVShaderId); + glDetachShader(RLGL.State.defaultShader.id, RLGL.State.defaultFShaderId); + glDeleteShader(RLGL.State.defaultVShaderId); + glDeleteShader(RLGL.State.defaultFShaderId); + + glDeleteProgram(RLGL.State.defaultShader.id); + + RL_FREE(RLGL.State.defaultShader.locs); + + TRACELOG(LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShader.id); +} + +#if defined(SUPPORT_GL_DETAILS_INFO) +// Get compressed format official GL identifier name +static char *rlGetCompressedFormatName(int format) +{ + static char compName[64] = { 0 }; + memset(compName, 0, 64); + + switch (format) + { + // GL_EXT_texture_compression_s3tc + case 0x83F0: strcpy(compName, "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"); break; + case 0x83F1: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"); break; + case 0x83F2: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"); break; + case 0x83F3: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"); break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: strcpy(compName, "GL_COMPRESSED_RGB_FXT1_3DFX"); break; + case 0x86B1: strcpy(compName, "GL_COMPRESSED_RGBA_FXT1_3DFX"); break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: strcpy(compName, "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"); break; + case 0x8C01: strcpy(compName, "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"); break; + case 0x8C02: strcpy(compName, "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"); break; + case 0x8C03: strcpy(compName, "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"); break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: strcpy(compName, "GL_ETC1_RGB8_OES"); break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: strcpy(compName, "GL_COMPRESSED_RED_RGTC1"); break; + case 0x8DBC: strcpy(compName, "GL_COMPRESSED_SIGNED_RED_RGTC1"); break; + case 0x8DBD: strcpy(compName, "GL_COMPRESSED_RG_RGTC2"); break; + case 0x8DBE: strcpy(compName, "GL_COMPRESSED_SIGNED_RG_RGTC2"); break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: strcpy(compName, "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"); break; + case 0x8E8D: strcpy(compName, "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"); break; + case 0x8E8E: strcpy(compName, "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"); break; + case 0x8E8F: strcpy(compName, "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"); break; + // GL_ARB_ES3_compatibility + case 0x9274: strcpy(compName, "GL_COMPRESSED_RGB8_ETC2"); break; + case 0x9275: strcpy(compName, "GL_COMPRESSED_SRGB8_ETC2"); break; + case 0x9276: strcpy(compName, "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"); break; + case 0x9277: strcpy(compName, "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"); break; + case 0x9278: strcpy(compName, "GL_COMPRESSED_RGBA8_ETC2_EAC"); break; + case 0x9279: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"); break; + case 0x9270: strcpy(compName, "GL_COMPRESSED_R11_EAC"); break; + case 0x9271: strcpy(compName, "GL_COMPRESSED_SIGNED_R11_EAC"); break; + case 0x9272: strcpy(compName, "GL_COMPRESSED_RG11_EAC"); break; + case 0x9273: strcpy(compName, "GL_COMPRESSED_SIGNED_RG11_EAC"); break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"); break; + case 0x93B1: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"); break; + case 0x93B2: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"); break; + case 0x93B3: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"); break; + case 0x93B4: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"); break; + case 0x93B5: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"); break; + case 0x93B6: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"); break; + case 0x93B7: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"); break; + case 0x93B8: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"); break; + case 0x93B9: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"); break; + case 0x93BA: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"); break; + case 0x93BB: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"); break; + case 0x93BC: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"); break; + case 0x93BD: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"); break; + case 0x93D0: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"); break; + case 0x93D1: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"); break; + case 0x93D2: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"); break; + case 0x93D3: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"); break; + case 0x93D4: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"); break; + case 0x93D5: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"); break; + case 0x93D6: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"); break; + case 0x93D7: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"); break; + case 0x93D8: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"); break; + case 0x93D9: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"); break; + case 0x93DA: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"); break; + case 0x93DB: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"); break; + case 0x93DC: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"); break; + case 0x93DD: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"); break; + default: strcpy(compName, "GL_COMPRESSED_UNKNOWN"); break; + } + + return compName; +} +#endif // SUPPORT_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_11) +// Mipmaps data is generated after image data +// NOTE: Only works with RGBA (4 bytes) data! +static int rlGenerateMipmapsData(unsigned char *data, int baseWidth, int baseHeight) +{ + int mipmapCount = 1; // Required mipmap levels count (including base level) + int width = baseWidth; + int height = baseHeight; + int size = baseWidth*baseHeight*4; // Size in bytes (will include mipmaps...), RGBA only + + // Count mipmap levels required + while ((width != 1) && (height != 1)) + { + width /= 2; + height /= 2; + + TRACELOGD("TEXTURE: Next mipmap size: %i x %i", width, height); + + mipmapCount++; + + size += (width*height*4); // Add mipmap size (in bytes) + } + + TRACELOGD("TEXTURE: Total mipmaps required: %i", mipmapCount); + TRACELOGD("TEXTURE: Total size of data required: %i", size); + + unsigned char *temp = RL_REALLOC(data, size); + + if (temp != NULL) data = temp; + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to re-allocate required mipmaps memory"); + + width = baseWidth; + height = baseHeight; + size = (width*height*4); + + // Generate mipmaps + // NOTE: Every mipmap data is stored after data + Color *image = (Color *)RL_MALLOC(width*height*sizeof(Color)); + Color *mipmap = NULL; + int offset = 0; + int j = 0; + + for (int i = 0; i < size; i += 4) + { + image[j].r = data[i]; + image[j].g = data[i + 1]; + image[j].b = data[i + 2]; + image[j].a = data[i + 3]; + j++; + } + + TRACELOGD("TEXTURE: Mipmap base size (%ix%i)", width, height); + + for (int mip = 1; mip < mipmapCount; mip++) + { + mipmap = rlGenNextMipmapData(image, width, height); + + offset += (width*height*4); // Size of last mipmap + j = 0; + + width /= 2; + height /= 2; + size = (width*height*4); // Mipmap size to store after offset + + // Add mipmap to data + for (int i = 0; i < size; i += 4) + { + data[offset + i] = mipmap[j].r; + data[offset + i + 1] = mipmap[j].g; + data[offset + i + 2] = mipmap[j].b; + data[offset + i + 3] = mipmap[j].a; + j++; + } + + RL_FREE(image); + + image = mipmap; + mipmap = NULL; + } + + RL_FREE(mipmap); // free mipmap data + + return mipmapCount; +} + +// Manual mipmap generation (basic scaling algorithm) +static Color *rlGenNextMipmapData(Color *srcData, int srcWidth, int srcHeight) +{ + int x2, y2; + Color prow, pcol; + + int width = srcWidth/2; + int height = srcHeight/2; + + Color *mipmap = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + // Scaling algorithm works perfectly (box-filter) + for (int y = 0; y < height; y++) + { + y2 = 2*y; + + for (int x = 0; x < width; x++) + { + x2 = 2*x; + + prow.r = (srcData[y2*srcWidth + x2].r + srcData[y2*srcWidth + x2 + 1].r)/2; + prow.g = (srcData[y2*srcWidth + x2].g + srcData[y2*srcWidth + x2 + 1].g)/2; + prow.b = (srcData[y2*srcWidth + x2].b + srcData[y2*srcWidth + x2 + 1].b)/2; + prow.a = (srcData[y2*srcWidth + x2].a + srcData[y2*srcWidth + x2 + 1].a)/2; + + pcol.r = (srcData[(y2+1)*srcWidth + x2].r + srcData[(y2+1)*srcWidth + x2 + 1].r)/2; + pcol.g = (srcData[(y2+1)*srcWidth + x2].g + srcData[(y2+1)*srcWidth + x2 + 1].g)/2; + pcol.b = (srcData[(y2+1)*srcWidth + x2].b + srcData[(y2+1)*srcWidth + x2 + 1].b)/2; + pcol.a = (srcData[(y2+1)*srcWidth + x2].a + srcData[(y2+1)*srcWidth + x2 + 1].a)/2; + + mipmap[y*width + x].r = (prow.r + pcol.r)/2; + mipmap[y*width + x].g = (prow.g + pcol.g)/2; + mipmap[y*width + x].b = (prow.b + pcol.b)/2; + mipmap[y*width + x].a = (prow.a + pcol.a)/2; + } + } + + TRACELOGD("TEXTURE: Mipmap generated successfully (%ix%i)", width, height); + + return mipmap; +} +#endif // GRAPHICS_API_OPENGL_11 + +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +static int rlGetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} +#endif // RLGL_IMPLEMENTATION diff --git a/raylib/rmem.h b/raylib/rmem.h new file mode 100644 index 0000000..dbf417f --- /dev/null +++ b/raylib/rmem.h @@ -0,0 +1,739 @@ +/********************************************************************************************** +* +* rmem - raylib memory pool and objects pool +* +* A quick, efficient, and minimal free list and arena-based allocator +* +* PURPOSE: +* - A quicker, efficient memory allocator alternative to 'malloc' and friends. +* - Reduce the possibilities of memory leaks for beginner developers using Raylib. +* - Being able to flexibly range check memory if necessary. +* +* CONFIGURATION: +* +* #define RMEM_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2019 Kevin 'Assyrianic' Yonan (@assyrianic) and reviewed by Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RMEM_H +#define RMEM_H + +#include +#include + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMEMAPI __declspec(dllexport) // We are building library as a Win32 shared library (.dll) +#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMEMAPI __declspec(dllimport) // We are using library as a Win32 shared library (.dll) +#else + #define RMEMAPI // We are building or using library as a static library (or Linux shared library) +#endif + +#define RMEM_VERSION "v1.3" // changelog at bottom of header. + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Memory Pool +typedef struct MemNode MemNode; +struct MemNode { + size_t size; + MemNode *next, *prev; +}; + +// Freelist implementation +typedef struct AllocList { + MemNode *head, *tail; + size_t len; +} AllocList; + +// Arena allocator. +typedef struct Arena { + uintptr_t mem, offs; + size_t size; +} Arena; + + +enum { + MEMPOOL_BUCKET_SIZE = 8, + MEMPOOL_BUCKET_BITS = (sizeof(uintptr_t) >> 1) + 1, + MEM_SPLIT_THRESHOLD = sizeof(uintptr_t) * 4 +}; + +typedef struct MemPool { + AllocList large, buckets[MEMPOOL_BUCKET_SIZE]; + Arena arena; +} MemPool; + + +// Object Pool +typedef struct ObjPool { + uintptr_t mem, offs; + size_t objSize, freeBlocks, memSize; +} ObjPool; + + +// Double-Ended Stack aka Deque +typedef struct BiStack { + uintptr_t mem, front, back; + size_t size; +} BiStack; + + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//------------------------------------------------------------------------------------ +// Functions Declaration - Memory Pool +//------------------------------------------------------------------------------------ +RMEMAPI MemPool CreateMemPool(size_t bytes); +RMEMAPI MemPool CreateMemPoolFromBuffer(void *buf, size_t bytes); +RMEMAPI void DestroyMemPool(MemPool *mempool); + +RMEMAPI void *MemPoolAlloc(MemPool *mempool, size_t bytes); +RMEMAPI void *MemPoolRealloc(MemPool *mempool, void *ptr, size_t bytes); +RMEMAPI void MemPoolFree(MemPool *mempool, void *ptr); +RMEMAPI void MemPoolCleanUp(MemPool *mempool, void **ptrref); +RMEMAPI void MemPoolReset(MemPool *mempool); +RMEMAPI size_t GetMemPoolFreeMemory(const MemPool mempool); + +//------------------------------------------------------------------------------------ +// Functions Declaration - Object Pool +//------------------------------------------------------------------------------------ +RMEMAPI ObjPool CreateObjPool(size_t objsize, size_t len); +RMEMAPI ObjPool CreateObjPoolFromBuffer(void *buf, size_t objsize, size_t len); +RMEMAPI void DestroyObjPool(ObjPool *objpool); + +RMEMAPI void *ObjPoolAlloc(ObjPool *objpool); +RMEMAPI void ObjPoolFree(ObjPool *objpool, void *ptr); +RMEMAPI void ObjPoolCleanUp(ObjPool *objpool, void **ptrref); + +//------------------------------------------------------------------------------------ +// Functions Declaration - Double-Ended Stack +//------------------------------------------------------------------------------------ +RMEMAPI BiStack CreateBiStack(size_t len); +RMEMAPI BiStack CreateBiStackFromBuffer(void *buf, size_t len); +RMEMAPI void DestroyBiStack(BiStack *destack); + +RMEMAPI void *BiStackAllocFront(BiStack *destack, size_t len); +RMEMAPI void *BiStackAllocBack(BiStack *destack, size_t len); + +RMEMAPI void BiStackResetFront(BiStack *destack); +RMEMAPI void BiStackResetBack(BiStack *destack); +RMEMAPI void BiStackResetAll(BiStack *destack); + +RMEMAPI intptr_t BiStackMargins(BiStack destack); + +#ifdef __cplusplus +} +#endif + +#endif // RMEM_H + +/*********************************************************************************** +* +* RMEM IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RMEM_IMPLEMENTATION) + +#include // Required for: +#include // Required for: +#include // Required for: + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Make sure restrict type qualifier for pointers is defined +// NOTE: Not supported by C++, it is a C only keyword +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) || defined(_MSC_VER) + #ifndef restrict + #define restrict __restrict + #endif +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static inline size_t __AlignSize(const size_t size, const size_t align) +{ + return (size + (align - 1)) & -align; +} + +static MemNode *__SplitMemNode(MemNode *const node, const size_t bytes) +{ + uintptr_t n = ( uintptr_t )node; + MemNode *const r = ( MemNode* )(n + (node->size - bytes)); + node->size -= bytes; + r->size = bytes; + return r; +} + +static void __InsertMemNodeBefore(AllocList *const list, MemNode *const insert, MemNode *const curr) +{ + insert->next = curr; + if (curr->prev==NULL) list->head = insert; + else + { + insert->prev = curr->prev; + curr->prev->next = insert; + } + curr->prev = insert; +} + +static void __ReplaceMemNode(MemNode *const old, MemNode *const replace) +{ + replace->prev = old->prev; + replace->next = old->next; + if( old->prev != NULL ) + old->prev->next = replace; + if( old->next != NULL ) + old->next->prev = replace; +} + + +static MemNode *__RemoveMemNode(AllocList *const list, MemNode *const node) +{ + if (node->prev != NULL) node->prev->next = node->next; + else + { + list->head = node->next; + if (list->head != NULL) list->head->prev = NULL; + else list->tail = NULL; + } + + if (node->next != NULL) node->next->prev = node->prev; + else + { + list->tail = node->prev; + if (list->tail != NULL) list->tail->next = NULL; + else list->head = NULL; + } + list->len--; + return node; +} + +static MemNode *__FindMemNode(AllocList *const list, const size_t bytes) +{ + for (MemNode *node = list->head; node != NULL; node = node->next) + { + if (node->size < bytes) continue; + // close in size - reduce fragmentation by not splitting. + else if (node->size <= bytes + MEM_SPLIT_THRESHOLD) return __RemoveMemNode(list, node); + else return __SplitMemNode(node, bytes); + } + return NULL; +} + +static void __InsertMemNode(MemPool *const mempool, AllocList *const list, MemNode *const node, const bool is_bucket) +{ + if (list->head == NULL) + { + list->head = node; + list->len++; + } + else + { + for (MemNode *iter = list->head; iter != NULL; iter = iter->next) + { + if (( uintptr_t )iter == mempool->arena.offs) + { + mempool->arena.offs += iter->size; + __RemoveMemNode(list, iter); + iter = list->head; + } + const uintptr_t inode = ( uintptr_t )node; + const uintptr_t iiter = ( uintptr_t )iter; + const uintptr_t iter_end = iiter + iter->size; + const uintptr_t node_end = inode + node->size; + if (iter==node) return; + else if (iter < node) + { + // node was coalesced prior. + if (iter_end > inode) return; + else if (iter_end==inode && !is_bucket) + { + // if we can coalesce, do so. + iter->size += node->size; + return; + } + } + else if (iter > node) + { + // Address sort, lowest to highest aka ascending order. + if (iiter < node_end) return; + else if (iter==list->head && !is_bucket) + { + if (iter_end==inode) iter->size += node->size; + else if (node_end==iiter) + { + node->size += list->head->size; + node->next = list->head->next; + node->prev = NULL; + list->head = node; + } + else + { + node->next = iter; + node->prev = NULL; + iter->prev = node; + list->head = node; + list->len++; + } + return; + } + else if (iter_end==inode && !is_bucket) + { + // if we can coalesce, do so. + iter->size += node->size; + return; + } + else + { + __InsertMemNodeBefore(list, iter, node); + list->len++; + return; + } + } + } + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Memory Pool +//---------------------------------------------------------------------------------- + +MemPool CreateMemPool(const size_t size) +{ + MemPool mempool = { 0 }; + + if (size == 0) return mempool; + else + { + // Align the mempool size to at least the size of an alloc node. + uint8_t *const restrict buf = malloc(size*sizeof *buf); + if (buf==NULL) return mempool; + else + { + mempool.arena.size = size; + mempool.arena.mem = ( uintptr_t )buf; + mempool.arena.offs = mempool.arena.mem + mempool.arena.size; + return mempool; + } + } +} + +MemPool CreateMemPoolFromBuffer(void *const restrict buf, const size_t size) +{ + MemPool mempool = { 0 }; + if ((size == 0) || (buf == NULL) || (size <= sizeof(MemNode))) return mempool; + else + { + mempool.arena.size = size; + mempool.arena.mem = ( uintptr_t )buf; + mempool.arena.offs = mempool.arena.mem + mempool.arena.size; + return mempool; + } +} + +void DestroyMemPool(MemPool *const restrict mempool) +{ + if (mempool->arena.mem == 0) return; + else + { + void *const restrict ptr = ( void* )mempool->arena.mem; + free(ptr); + *mempool = (MemPool){ 0 }; + } +} + +void *MemPoolAlloc(MemPool *const mempool, const size_t size) +{ + if ((size == 0) || (size > mempool->arena.size)) return NULL; + else + { + MemNode *new_mem = NULL; + const size_t ALLOC_SIZE = __AlignSize(size + sizeof *new_mem, sizeof(intptr_t)); + const size_t BUCKET_SLOT = (ALLOC_SIZE >> MEMPOOL_BUCKET_BITS) - 1; + + // If the size is small enough, let's check if our buckets has a fitting memory block. + if (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) + { + new_mem = __FindMemNode(&mempool->buckets[BUCKET_SLOT], ALLOC_SIZE); + } + else if (mempool->large.head != NULL) + { + new_mem = __FindMemNode(&mempool->large, ALLOC_SIZE); + } + + if (new_mem == NULL) + { + // not enough memory to support the size! + if ((mempool->arena.offs - ALLOC_SIZE) < mempool->arena.mem) return NULL; + else + { + // Couldn't allocate from a freelist, allocate from available mempool. + // Subtract allocation size from the mempool. + mempool->arena.offs -= ALLOC_SIZE; + + // Use the available mempool space as the new node. + new_mem = ( MemNode* )mempool->arena.offs; + new_mem->size = ALLOC_SIZE; + } + } + + // Visual of the allocation block. + // -------------- + // | mem size | lowest addr of block + // | next node | 12 byte (32-bit) header + // | prev node | 24 byte (64-bit) header + // |------------| + // | alloc'd | + // | memory | + // | space | highest addr of block + // -------------- + new_mem->next = new_mem->prev = NULL; + uint8_t *const restrict final_mem = ( uint8_t* )new_mem + sizeof *new_mem; + return memset(final_mem, 0, new_mem->size - sizeof *new_mem); + } +} + +void *MemPoolRealloc(MemPool *const restrict mempool, void *const ptr, const size_t size) +{ + if (size > mempool->arena.size) return NULL; + // NULL ptr should make this work like regular Allocation. + else if (ptr == NULL) return MemPoolAlloc(mempool, size); + else if ((uintptr_t)ptr - sizeof(MemNode) < mempool->arena.mem) return NULL; + else + { + MemNode *const node = ( MemNode* )(( uint8_t* )ptr - sizeof *node); + const size_t NODE_SIZE = sizeof *node; + uint8_t *const resized_block = MemPoolAlloc(mempool, size); + if (resized_block == NULL) return NULL; + else + { + MemNode *const resized = ( MemNode* )(resized_block - sizeof *resized); + memmove(resized_block, ptr, (node->size > resized->size)? (resized->size - NODE_SIZE) : (node->size - NODE_SIZE)); + MemPoolFree(mempool, ptr); + return resized_block; + } + } +} + +void MemPoolFree(MemPool *const restrict mempool, void *const ptr) +{ + const uintptr_t p = ( uintptr_t )ptr; + if ((ptr == NULL) || (p - sizeof(MemNode) < mempool->arena.mem)) return; + else + { + // Behind the actual pointer data is the allocation info. + const uintptr_t block = p - sizeof(MemNode); + MemNode *const mem_node = ( MemNode* )block; + const size_t BUCKET_SLOT = (mem_node->size >> MEMPOOL_BUCKET_BITS) - 1; + + // Make sure the pointer data is valid. + if ((block < mempool->arena.offs) || + ((block - mempool->arena.mem) > mempool->arena.size) || + (mem_node->size == 0) || + (mem_node->size > mempool->arena.size)) return; + // If the mem_node is right at the arena offs, then merge it back to the arena. + else if (block == mempool->arena.offs) + { + mempool->arena.offs += mem_node->size; + } + else + { + // try to place it into bucket or large freelist. + struct AllocList *const l = (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) ? &mempool->buckets[BUCKET_SLOT] : &mempool->large; + __InsertMemNode(mempool, l, mem_node, (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE)); + } + } +} + +void MemPoolCleanUp(MemPool *const restrict mempool, void **const ptrref) +{ + if ((ptrref == NULL) || (*ptrref == NULL)) return; + else + { + MemPoolFree(mempool, *ptrref); + *ptrref = NULL; + } +} + +size_t GetMemPoolFreeMemory(const MemPool mempool) +{ + size_t total_remaining = mempool.arena.offs - mempool.arena.mem; + + for (MemNode *n=mempool.large.head; n != NULL; n = n->next) total_remaining += n->size; + + for (size_t i=0; inext) total_remaining += n->size; + + return total_remaining; +} + +void MemPoolReset(MemPool *const mempool) +{ + mempool->large.head = mempool->large.tail = NULL; + mempool->large.len = 0; + for (size_t i = 0; i < MEMPOOL_BUCKET_SIZE; i++) + { + mempool->buckets[i].head = mempool->buckets[i].tail = NULL; + mempool->buckets[i].len = 0; + } + mempool->arena.offs = mempool->arena.mem + mempool->arena.size; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Object Pool +//---------------------------------------------------------------------------------- + +ObjPool CreateObjPool(const size_t objsize, const size_t len) +{ + ObjPool objpool = { 0 }; + if ((len == 0) || (objsize == 0)) return objpool; + else + { + const size_t aligned_size = __AlignSize(objsize, sizeof(size_t)); + uint8_t *const restrict buf = calloc(len, aligned_size); + if (buf == NULL) return objpool; + objpool.objSize = aligned_size; + objpool.memSize = objpool.freeBlocks = len; + objpool.mem = ( uintptr_t )buf; + + for (size_t i=0; imem == 0) return; + else + { + void *const restrict ptr = ( void* )objpool->mem; + free(ptr); + *objpool = (ObjPool){0}; + } +} + +void *ObjPoolAlloc(ObjPool *const objpool) +{ + if (objpool->freeBlocks > 0) + { + // For first allocation, head points to the very first index. + // Head = &pool[0]; + // ret = Head == ret = &pool[0]; + size_t *const restrict block = ( size_t* )objpool->offs; + objpool->freeBlocks--; + + // after allocating, we set head to the address of the index that *Head holds. + // Head = &pool[*Head * pool.objsize]; + objpool->offs = (objpool->freeBlocks != 0)? objpool->mem + (*block*objpool->objSize) : 0; + return memset(block, 0, objpool->objSize); + } + else return NULL; +} + +void ObjPoolFree(ObjPool *const restrict objpool, void *const ptr) +{ + uintptr_t block = (uintptr_t)ptr; + if ((ptr == NULL) || (block < objpool->mem) || (block > objpool->mem + objpool->memSize*objpool->objSize)) return; + else + { + // When we free our pointer, we recycle the pointer space to store the previous index and then we push it as our new head. + // *p = index of Head in relation to the buffer; + // Head = p; + size_t *const restrict index = ( size_t* )block; + *index = (objpool->offs != 0)? (objpool->offs - objpool->mem)/objpool->objSize : objpool->memSize; + objpool->offs = block; + objpool->freeBlocks++; + } +} + +void ObjPoolCleanUp(ObjPool *const restrict objpool, void **const restrict ptrref) +{ + if (ptrref == NULL) return; + else + { + ObjPoolFree(objpool, *ptrref); + *ptrref = NULL; + } +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Double-Ended Stack +//---------------------------------------------------------------------------------- +BiStack CreateBiStack(const size_t len) +{ + BiStack destack = { 0 }; + if (len == 0) return destack; + + uint8_t *const buf = malloc(len*sizeof *buf); + if (buf==NULL) return destack; + destack.size = len; + destack.mem = ( uintptr_t )buf; + destack.front = destack.mem; + destack.back = destack.mem + len; + return destack; +} + +BiStack CreateBiStackFromBuffer(void *const buf, const size_t len) +{ + BiStack destack = { 0 }; + if (len == 0 || buf == NULL) return destack; + else + { + destack.size = len; + destack.mem = destack.front = ( uintptr_t )buf; + destack.back = destack.mem + len; + return destack; + } +} + +void DestroyBiStack(BiStack *const restrict destack) +{ + if (destack->mem == 0) return; + else + { + uint8_t *const restrict buf = ( uint8_t* )destack->mem; + free(buf); + *destack = (BiStack){0}; + } +} + +void *BiStackAllocFront(BiStack *const restrict destack, const size_t len) +{ + if (destack->mem == 0) return NULL; + else + { + const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); + // front end arena is too high! + if (destack->front + ALIGNED_LEN >= destack->back) return NULL; + else + { + uint8_t *const restrict ptr = ( uint8_t* )destack->front; + destack->front += ALIGNED_LEN; + return ptr; + } + } +} + +void *BiStackAllocBack(BiStack *const restrict destack, const size_t len) +{ + if (destack->mem == 0) return NULL; + else + { + const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); + // back end arena is too low + if (destack->back - ALIGNED_LEN <= destack->front) return NULL; + else + { + destack->back -= ALIGNED_LEN; + uint8_t *const restrict ptr = ( uint8_t* )destack->back; + return ptr; + } + } +} + +void BiStackResetFront(BiStack *const destack) +{ + if (destack->mem == 0) return; + else destack->front = destack->mem; +} + +void BiStackResetBack(BiStack *const destack) +{ + if (destack->mem == 0) return; + else destack->back = destack->mem + destack->size; +} + +void BiStackResetAll(BiStack *const destack) +{ + BiStackResetBack(destack); + BiStackResetFront(destack); +} + +inline intptr_t BiStackMargins(const BiStack destack) +{ + return destack.back - destack.front; +} + +#endif // RMEM_IMPLEMENTATION + +/******* + * Changelog + * v1.0: First Creation. + * v1.1: bug patches for the mempool and addition of object pool. + * v1.2: addition of bidirectional arena. + * v1.3: + * optimizations of allocators. + * renamed 'Stack' to 'Arena'. + * replaced certain define constants with an anonymous enum. + * refactored MemPool to no longer require active or deferred defragging. + ********/ diff --git a/raylib/rnet.h b/raylib/rnet.h new file mode 100644 index 0000000..439b105 --- /dev/null +++ b/raylib/rnet.h @@ -0,0 +1,2256 @@ +/********************************************************************************************** +* +* rnet - A simple and easy-to-use network module for raylib +* +* FEATURES: +* - Provides a simple and (hopefully) easy to use wrapper around the Berkeley socket API +* +* INSPIRED BY: +* SFML Sockets - https://www.sfml-dev.org/documentation/2.5.1/classsf_1_1Socket.php +* SDL_net - https://www.libsdl.org/projects/SDL_net/ +* BSD Sockets - https://www.gnu.org/software/libc/manual/html_node/Sockets.html +* BEEJ - https://beej.us/guide/bgnet/html/single/bgnet.html +* Winsock2 - https://docs.microsoft.com/en-us/windows/desktop/api/winsock2 +* +* CONTRIBUTORS: +* Jak Barnes (github: @syphonx) (Feb. 2019) - Initial version +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2019-2020 Jak Barnes (@syphonx) and Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RNET_H +#define RNET_H + +#include // Required for limits +#include // Required for platform type sizes + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Undefine any conflicting windows.h symbols +// If defined, the following flags inhibit definition of the indicated items. +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI defines and routines +#define NOKERNEL // All KERNEL defines and routines +#define NOUSER // All USER defines and routines +#define NONLS // All NLS defines and routines +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff. +#define NOHELP // Help engine interface. +#define NOPROFILER // Profiler interface. +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions +#define MMNOSOUND + +// Allow custom memory allocators +#ifndef RNET_MALLOC + #define RNET_MALLOC(sz) malloc(sz) +#endif +#ifndef RNET_CALLOC + #define RNET_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RNET_FREE + #define RNET_FREE(p) free(p) +#endif + +//---------------------------------------------------------------------------------- +// Platform type definitions +// From: https://github.com/DFHack/clsocket/blob/master/src/Host.h +//---------------------------------------------------------------------------------- + +#ifdef WIN32 +typedef int socklen_t; +#endif + +#ifndef RESULT_SUCCESS +# define RESULT_SUCCESS 0 +#endif // RESULT_SUCCESS + +#ifndef RESULT_FAILURE +# define RESULT_FAILURE 1 +#endif // RESULT_FAILURE + +#ifndef htonll +# ifdef _BIG_ENDIAN +# define htonll(x) (x) +# define ntohll(x) (x) +# else +# define htonll(x) ((((uint64) htonl(x)) << 32) + htonl(x >> 32)) +# define ntohll(x) ((((uint64) ntohl(x)) << 32) + ntohl(x >> 32)) +# endif // _BIG_ENDIAN +#endif // htonll + +//---------------------------------------------------------------------------------- +// Platform specific network includes +// From: https://github.com/SDL-mirror/SDL_net/blob/master/SDLnetsys.h +//---------------------------------------------------------------------------------- + +// Include system network headers +#if defined(_WIN32) // Windows + #define __USE_W32_SOCKETS + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + #define IPTOS_LOWDELAY 0x10 +#else // Unix + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#ifndef INVALID_SOCKET + #define INVALID_SOCKET ~(0) +#endif + +#ifndef __USE_W32_SOCKETS + #define closesocket close + #define SOCKET int + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 +#endif + +#ifdef __USE_W32_SOCKETS + #ifndef EINTR + #define EINTR WSAEINTR + #endif +#endif + +//---------------------------------------------------------------------------------- +// Module defines +//---------------------------------------------------------------------------------- + +// Network connection related defines +#define SOCKET_MAX_SET_SIZE 32 // Maximum sockets in a set +#define SOCKET_MAX_QUEUE_SIZE 16 // Maximum socket queue size +#define SOCKET_MAX_SOCK_OPTS 4 // Maximum socket options +#define SOCKET_MAX_UDPCHANNELS 32 // Maximum UDP channels +#define SOCKET_MAX_UDPADDRESSES 4 // Maximum bound UDP addresses + +// Network address related defines +#define ADDRESS_IPV4_ADDRSTRLEN 22 // IPv4 string length +#define ADDRESS_IPV6_ADDRSTRLEN 65 // IPv6 string length +#define ADDRESS_TYPE_ANY 0 // AF_UNSPEC +#define ADDRESS_TYPE_IPV4 2 // AF_INET +#define ADDRESS_TYPE_IPV6 23 // AF_INET6 +#define ADDRESS_MAXHOST 1025 // Max size of a fully-qualified domain name +#define ADDRESS_MAXSERV 32 // Max size of a service name + +// Network address related defines +#define ADDRESS_ANY (unsigned long)0x00000000 +#define ADDRESS_LOOPBACK 0x7f000001 +#define ADDRESS_BROADCAST (unsigned long)0xffffffff +#define ADDRESS_NONE 0xffffffff + +// Network resolution related defines +#define NAME_INFO_DEFAULT 0x00 // No flags set +#define NAME_INFO_NOFQDN 0x01 // Only return nodename portion for local hosts +#define NAME_INFO_NUMERICHOST 0x02 // Return numeric form of the host's address +#define NAME_INFO_NAMEREQD 0x04 // Error if the host's name not in DNS +#define NAME_INFO_NUMERICSERV 0x08 // Return numeric form of the service (port #) +#define NAME_INFO_DGRAM 0x10 // Service is a datagram service + +// Address resolution related defines +#if defined(_WIN32) + #define ADDRESS_INFO_PASSIVE (0x00000001) // Socket address will be used in bind() call + #define ADDRESS_INFO_CANONNAME (0x00000002) // Return canonical name in first ai_canonname + #define ADDRESS_INFO_NUMERICHOST (0x00000004) // Nodename must be a numeric address string + #define ADDRESS_INFO_NUMERICSERV (0x00000008) // Servicename must be a numeric port number + #define ADDRESS_INFO_DNS_ONLY (0x00000010) // Restrict queries to unicast DNS only (no LLMNR, netbios, etc.) + #define ADDRESS_INFO_ALL (0x00000100) // Query both IP6 and IP4 with AI_V4MAPPED + #define ADDRESS_INFO_ADDRCONFIG (0x00000400) // Resolution only if global address configured + #define ADDRESS_INFO_V4MAPPED (0x00000800) // On v6 failure, query v4 and convert to V4MAPPED format + #define ADDRESS_INFO_NON_AUTHORITATIVE (0x00004000) // LUP_NON_AUTHORITATIVE + #define ADDRESS_INFO_SECURE (0x00008000) // LUP_SECURE + #define ADDRESS_INFO_RETURN_PREFERRED_NAMES (0x00010000) // LUP_RETURN_PREFERRED_NAMES + #define ADDRESS_INFO_FQDN (0x00020000) // Return the FQDN in ai_canonname + #define ADDRESS_INFO_FILESERVER (0x00040000) // Resolving fileserver name resolution + #define ADDRESS_INFO_DISABLE_IDN_ENCODING (0x00080000) // Disable Internationalized Domain Names handling + #define ADDRESS_INFO_EXTENDED (0x80000000) // Indicates this is extended ADDRINFOEX(2/..) struct + #define ADDRESS_INFO_RESOLUTION_HANDLE (0x40000000) // Request resolution handle +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Boolean type +#ifdef _WIN32 + #include +#else +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum { false, true } bool; +#endif +#endif + +typedef enum { + SOCKET_TCP = 0, // SOCK_STREAM + SOCKET_UDP = 1 // SOCK_DGRAM +} SocketType; + +// Network typedefs +typedef uint32_t SocketChannel; +typedef struct _AddressInformation *AddressInformation; +typedef struct _SocketAddress *SocketAddress; +typedef struct _SocketAddressIPv4 *SocketAddressIPv4; +typedef struct _SocketAddressIPv6 *SocketAddressIPv6; +typedef struct _SocketAddressStorage *SocketAddressStorage; + +// IPAddress definition (in network byte order) +typedef struct IPAddress { + unsigned long host; // 32-bit IPv4 host address + unsigned short port; // 16-bit protocol port +} IPAddress; + +typedef struct UDPChannel { + int numbound; // The total number of addresses this channel is bound to + IPAddress address[SOCKET_MAX_UDPADDRESSES]; // The list of remote addresses this channel is bound to +} UDPChannel; + +// An option ID, value, sizeof(value) tuple for setsockopt(2). +typedef struct SocketOpt { + int id; // Socked option id + int valueLen; // Socked option value len + void *value; // Socked option value data +} SocketOpt; + +typedef struct Socket { + int ready; // Is the socket ready? i.e. has information + int status; // The last status code to have occured using this socket + bool isServer; // Is this socket a server socket (i.e. TCP/UDP Listen Server) + SocketChannel channel; // The socket handle id + SocketType type; // Is this socket a TCP or UDP socket? + + bool isIPv6; // Is this socket address an ipv6 address? + SocketAddressIPv4 addripv4; // The host/target IPv4 for this socket (in network byte order) + SocketAddressIPv6 addripv6; // The host/target IPv6 for this socket (in network byte order) + + struct UDPChannel binding[SOCKET_MAX_UDPCHANNELS]; // The amount of channels (if UDP) this socket is bound to +} Socket; + +// Configuration for a socket +typedef struct SocketConfig { + SocketType type; // The type of socket, TCP/UDP + char *host; // The host address in xxx.xxx.xxx.xxx form + char *port; // The target port/service in the form "http" or "25565" + bool server; // Listen for incoming clients? + bool nonblocking; // non-blocking operation? + int backlog_size; // set a custom backlog size + SocketOpt sockopts[SOCKET_MAX_SOCK_OPTS]; +} SocketConfig; + +typedef struct SocketDataPacket { + IPAddress address; // The source/dest address of an incoming/outgoing packet + int channel; // The src/dst channel of the packet + int maxlen; // The size of the data buffer + int status; // Packet status after sending + unsigned int len; // The length of the packet data + unsigned char *data; // The packet data +} SocketDataPacket; + +// Result from calling open with a given config +typedef struct SocketResult { + int status; // Socket result state + Socket *socket; // Socket ref +} SocketResult; + +typedef struct SocketSet { + int numsockets; // Socket set count + int maxsockets; // Socket set max + struct Socket **sockets; // Sockets array +} SocketSet; + +// Packet type +typedef struct Packet { + uint32_t size; // The total size of bytes in data + uint32_t offs; // The offset to data access + uint32_t maxs; // The max size of data + uint8_t *data; // Data stored in network byte order +} Packet; + + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +// Initialisation and cleanup +bool InitNetworkDevice(void); +void CloseNetworkDevice(void); + +// Address API +void ResolveIP(const char *ip, const char *service, int flags, char *outhost, char *outserv); +int ResolveHost(const char *address, const char *service, int addressType, int flags, AddressInformation *outAddr); +int GetAddressFamily(AddressInformation address); +int GetAddressSocketType(AddressInformation address); +int GetAddressProtocol(AddressInformation address); +char *GetAddressCanonName(AddressInformation address); +char *GetAddressHostAndPort(AddressInformation address, char *outhost, unsigned short *outport); + +// Address Memory API +AddressInformation LoadAddress(void); +void UnloadAddress(AddressInformation *addressInfo); +AddressInformation *LoadAddressList(int size); + +// Socket API +bool SocketCreate(SocketConfig *config, SocketResult *result); +bool SocketBind(SocketConfig *config, SocketResult *result); +bool SocketListen(SocketConfig *config, SocketResult *result); +bool SocketConnect(SocketConfig *config, SocketResult *result); +Socket *SocketAccept(Socket *server, SocketConfig *config); + +// General Socket API +int SocketSend(Socket *sock, const void *datap, int len); +int SocketReceive(Socket *sock, void *data, int maxlen); +SocketAddressStorage SocketGetPeerAddress(Socket *sock); +const char *GetSocketAddressHost(SocketAddressStorage storage); +short GetSocketAddressPort(SocketAddressStorage storage); +void SocketClose(Socket *sock); + +// UDP Socket API +int SocketSetChannel(Socket *socket, int channel, const IPAddress *address); +void SocketUnsetChannel(Socket *socket, int channel); + +// UDP DataPacket API +SocketDataPacket *AllocPacket(int size); +int ResizePacket(SocketDataPacket *packet, int newsize); +void FreePacket(SocketDataPacket *packet); +SocketDataPacket **AllocPacketList(int count, int size); +void FreePacketList(SocketDataPacket **packets); + +// Socket Memory API +Socket *LoadSocket(void); +void UnloadSocket(Socket **sock); +SocketResult *LoadSocketResult(void); +void UnloadSocketResult(SocketResult **result); +SocketSet *LoadSocketSet(int max); +void UnloadSocketSet(SocketSet *sockset); + +// Socket I/O API +bool IsSocketReady(Socket *sock); +bool IsSocketConnected(Socket *sock); +int AddSocket(SocketSet *set, Socket *sock); +int RemoveSocket(SocketSet *set, Socket *sock); +int CheckSockets(SocketSet *set, unsigned int timeout); + +// Packet API +void PacketSend(Packet *packet); +void PacketReceive(Packet *packet); +void PacketWrite8(Packet *packet, uint16_t value); +void PacketWrite16(Packet *packet, uint16_t value); +void PacketWrite32(Packet *packet, uint32_t value); +void PacketWrite64(Packet *packet, uint64_t value); +uint16_t PacketRead8(Packet *packet); +uint16_t PacketRead16(Packet *packet); +uint32_t PacketRead32(Packet *packet); +uint64_t PacketRead64(Packet *packet); + +#ifdef __cplusplus +} +#endif + +#endif // RNET_H + +/*********************************************************************************** +* +* RNET IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RNET_IMPLEMENTATION) + +#include // Required for: assert() +#include // Required for: FILE, fopen(), fclose(), fread() +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strncmp() + +#define NET_DEBUG_ENABLED 1 + +#if defined(SUPPORT_TRACELOG) + #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) + + #if defined(SUPPORT_TRACELOG_DEBUG) + #define TRACELOGD(...) TraceLog(LOG_DEBUG, __VA_ARGS__) + #else + #define TRACELOGD(...) (void)0 + #endif +#else + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +typedef struct _SocketAddress +{ + struct sockaddr address; +} _SocketAddress; + +typedef struct _SocketAddressIPv4 +{ + struct sockaddr_in address; +} _SocketAddressIPv4; + +typedef struct _SocketAddressIPv6 +{ + struct sockaddr_in6 address; +} _SocketAddressIPv6; + +typedef struct _SocketAddressStorage +{ + struct sockaddr_storage address; +} _SocketAddressStorage; + +typedef struct _AddressInformation +{ + struct addrinfo addr; +} _AddressInformation; + +//---------------------------------------------------------------------------------- +// Local module Functions Declarations +//---------------------------------------------------------------------------------- +static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol); +static const char *SocketAddressToString(struct sockaddr_storage *sockaddr); +static bool IsIPv4Address(const char *ip); +static bool IsIPv6Address(const char *ip); +static void *GetSocketPortPtr(struct sockaddr_storage *sa); +static void *GetSocketAddressPtr(struct sockaddr_storage *sa); +static bool IsSocketValid(Socket *sock); +static void SocketSetLastError(int err); +static int SocketGetLastError(); +static char *SocketGetLastErrorString(); +static char *SocketErrorCodeToString(int err); +static bool SocketSetDefaults(SocketConfig *config); +static bool InitSocket(Socket *sock, struct addrinfo *addr); +static bool CreateSocket(SocketConfig *config, SocketResult *outresult); +static bool SocketSetBlocking(Socket *sock); +static bool SocketSetNonBlocking(Socket *sock); +static bool SocketSetOptions(SocketConfig *config, Socket *sock); +static void SocketSetHints(SocketConfig *config, struct addrinfo *hints); + +//---------------------------------------------------------------------------------- +// Local module Functions Definition +//---------------------------------------------------------------------------------- +// Print socket information +static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol) +{ + switch (family) + { + case AF_UNSPEC: TRACELOG(LOG_DEBUG, "\tFamily: Unspecified"); break; + case AF_INET: + { + TRACELOG(LOG_DEBUG, "\tFamily: AF_INET (IPv4)"); + TRACELOG(LOG_INFO, "\t- IPv4 address %s", SocketAddressToString(addr)); + } break; + case AF_INET6: + { + TRACELOG(LOG_DEBUG, "\tFamily: AF_INET6 (IPv6)"); + TRACELOG(LOG_INFO, "\t- IPv6 address %s", SocketAddressToString(addr)); + } break; + case AF_NETBIOS: + { + TRACELOG(LOG_DEBUG, "\tFamily: AF_NETBIOS (NetBIOS)"); + } break; + default: TRACELOG(LOG_DEBUG, "\tFamily: Other %ld", family); break; + } + + TRACELOG(LOG_DEBUG, "\tSocket type:"); + switch (socktype) + { + case 0: TRACELOG(LOG_DEBUG, "\t- Unspecified"); break; + case SOCK_STREAM: TRACELOG(LOG_DEBUG, "\t- SOCK_STREAM (stream)"); break; + case SOCK_DGRAM: TRACELOG(LOG_DEBUG, "\t- SOCK_DGRAM (datagram)"); break; + case SOCK_RAW: TRACELOG(LOG_DEBUG, "\t- SOCK_RAW (raw)"); break; + case SOCK_RDM: TRACELOG(LOG_DEBUG, "\t- SOCK_RDM (reliable message datagram)"); break; + case SOCK_SEQPACKET: TRACELOG(LOG_DEBUG, "\t- SOCK_SEQPACKET (pseudo-stream packet)"); break; + default: TRACELOG(LOG_DEBUG, "\t- Other %ld", socktype); break; + } + + TRACELOG(LOG_DEBUG, "\tProtocol:"); + switch (protocol) + { + case 0: TRACELOG(LOG_DEBUG, "\t- Unspecified"); break; + case IPPROTO_TCP: TRACELOG(LOG_DEBUG, "\t- IPPROTO_TCP (TCP)"); break; + case IPPROTO_UDP: TRACELOG(LOG_DEBUG, "\t- IPPROTO_UDP (UDP)"); break; + default: TRACELOG(LOG_DEBUG, "\t- Other %ld", protocol); break; + } +} + +// Convert network ordered socket address to human readable string (127.0.0.1) +static const char *SocketAddressToString(struct sockaddr_storage *sockaddr) +{ + //static const char* ipv6[INET6_ADDRSTRLEN]; + assert(sockaddr != NULL); + assert(sockaddr->ss_family == AF_INET || sockaddr->ss_family == AF_INET6); + + switch (sockaddr->ss_family) + { + case AF_INET: + { + //struct sockaddr_in *s = ((struct sockaddr_in *)sockaddr); + //return inet_ntop(AF_INET, &s->sin_addr, ipv6, INET_ADDRSTRLEN); // TODO. + } + break; + case AF_INET6: + { + //struct sockaddr_in6 *s = ((struct sockaddr_in6 *)sockaddr); + //return inet_ntop(AF_INET6, &s->sin6_addr, ipv6, INET6_ADDRSTRLEN); // TODO. + } + break; + } + + return NULL; +} + +// Check if the null terminated string ip is a valid IPv4 address +static bool IsIPv4Address(const char *ip) +{ + /* + struct sockaddr_in sa; + int result = inet_pton(AF_INET, ip, &(sa.sin_addr)); // TODO. + return (result != 0); + */ + return false; +} + +// Check if the null terminated string ip is a valid IPv6 address +static bool IsIPv6Address(const char *ip) +{ + /* + struct sockaddr_in6 sa; + int result = inet_pton(AF_INET6, ip, &(sa.sin6_addr)); // TODO. + return result != 0; + */ + return false; +} + +// Return a pointer to the port from the correct address family (IPv4, or IPv6) +static void *GetSocketPortPtr(struct sockaddr_storage *sa) +{ + if (sa->ss_family == AF_INET) + { + return &(((struct sockaddr_in *)sa)->sin_port); + } + + return &(((struct sockaddr_in6 *)sa)->sin6_port); +} + +// Return a pointer to the address from the correct address family (IPv4, or IPv6) +static void *GetSocketAddressPtr(struct sockaddr_storage *sa) +{ + if (sa->ss_family == AF_INET) + { + return &(((struct sockaddr_in *)sa)->sin_addr); + } + + return &(((struct sockaddr_in6 *)sa)->sin6_addr); +} + +// Is the socket in a valid state? +static bool IsSocketValid(Socket *sock) +{ + if (sock != NULL) + { + return (sock->channel != INVALID_SOCKET); + } + + return false; +} + +// Sets the error code that can be retrieved through the WSAGetLastError function. +static void SocketSetLastError(int err) +{ +#if defined(_WIN32) + WSASetLastError(err); +#else + errno = err; +#endif +} + +// Returns the error status for the last Sockets operation that failed +static int SocketGetLastError(void) +{ +#if defined(_WIN32) + return WSAGetLastError(); +#else + return errno; +#endif +} + +// Returns a human-readable string representing the last error message +static char *SocketGetLastErrorString(void) +{ + return SocketErrorCodeToString(SocketGetLastError()); +} + +// Returns a human-readable string representing the error message (err) +static char *SocketErrorCodeToString(int err) +{ +#if defined(_WIN32) + static char gaiStrErrorBuffer[GAI_STRERROR_BUFFER_SIZE]; + TRACELOG(LOG_INFO, gaiStrErrorBuffer, "%s", gai_strerror(err)); + return gaiStrErrorBuffer; +#else + return gai_strerror(err); +#endif +} + +// Set the defaults in the supplied SocketConfig if they're not already set +static bool SocketSetDefaults(SocketConfig *config) +{ + if (config->backlog_size == 0) config->backlog_size = SOCKET_MAX_QUEUE_SIZE; + + return true; +} + +// Create the socket channel +static bool InitSocket(Socket *sckt, struct addrinfo *address) +{ + switch (sckt->type) + { + case SOCKET_TCP: + { + if (address->ai_family == AF_INET) sckt->channel = socket(AF_INET, SOCK_STREAM, 0); + else sckt->channel = socket(AF_INET6, SOCK_STREAM, 0); + } break; + case SOCKET_UDP: + { + if (address->ai_family == AF_INET) sckt->channel = socket(AF_INET, SOCK_DGRAM, 0); + else sckt->channel = socket(AF_INET6, SOCK_DGRAM, 0); + } break; + default: TRACELOG(LOG_WARNING, "Invalid socket type specified."); break; + } + + return IsSocketValid(sckt); +} + +// CreateSocket() - Interally called by CreateSocket() +// +// This here is the bread and butter of the socket API, This function will +// attempt to open a socket, bind and listen to it based on the config passed in +// +// SocketConfig* config - Configuration for which socket to open +// SocketResult* result - The results of this function (if any, including errors) +// +// e.g. +// SocketConfig server_config = { SocketConfig client_config = { +// .host = "127.0.0.1", .host = "127.0.0.1", +// .port = 8080, .port = 8080, +// .server = true, }; +// .nonblocking = true, +// }; +// SocketResult server_res; SocketResult client_res; +static bool CreateSocket(SocketConfig *config, SocketResult *outresult) +{ + bool success = true; + int addrstatus; + struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?) + struct addrinfo *res; // A pointer to the resulting address list + + outresult->socket->channel = INVALID_SOCKET; + outresult->status = RESULT_FAILURE; + + // Set the socket type + outresult->socket->type = config->type; + + // Set the hints based on information in the config + // + // AI_CANONNAME Causes the ai_canonname of the result to the filled out with the host's canonical (real) name. + // AI_PASSIVE: Causes the result's IP address to be filled out with INADDR_ANY (IPv4)or in6addr_any (IPv6); + // Note: This causes a subsequent call to bind() to auto-fill the IP address + // of the struct sockaddr with the address of the current host. + // + SocketSetHints(config, &hints); + + // Populate address information + addrstatus = getaddrinfo(config->host, // e.g. "www.example.com" or IP (Can be null if AI_PASSIVE flag is set + config->port, // e.g. "http" or port number + &hints, // e.g. SOCK_STREAM/SOCK_DGRAM + &res // The struct to populate + ); + + // Did we succeed? + if (addrstatus != 0) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->socket->status)); + SocketSetLastError(0); + TRACELOG(LOG_WARNING, "Failed to get resolve host %s:%s: %s", config->host, config->port, SocketGetLastErrorString()); + + return (success = false); + } + else + { + char hoststr[NI_MAXHOST]; + char portstr[NI_MAXSERV]; + //socklen_t client_len = sizeof(struct sockaddr_storage); + //int rc = getnameinfo((struct sockaddr *)res->ai_addr, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + TRACELOG(LOG_INFO, "Successfully resolved host %s:%s", hoststr, portstr); + } + + // Walk the address information linked-list + struct addrinfo *it; + for (it = res; it != NULL; it = it->ai_next) + { + // Initialise the socket + if (!InitSocket(outresult->socket, it)) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->socket->status)); + SocketSetLastError(0); + continue; + } + + // Set socket options + if (!SocketSetOptions(config, outresult->socket)) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->socket->status)); + SocketSetLastError(0); + freeaddrinfo(res); + + return (success = false); + } + } + + if (!IsSocketValid(outresult->socket)) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->status)); + SocketSetLastError(0); + freeaddrinfo(res); + + return (success = false); + } + + if (success) + { + outresult->status = RESULT_SUCCESS; + outresult->socket->ready = 0; + outresult->socket->status = 0; + + if (!(config->type == SOCKET_UDP)) outresult->socket->isServer = config->server; + + switch (res->ai_addr->sa_family) + { + case AF_INET: + { + outresult->socket->addripv4 = (struct _SocketAddressIPv4 *)RNET_MALLOC(sizeof(*outresult->socket->addripv4)); + + if (outresult->socket->addripv4 != NULL) + { + memset(outresult->socket->addripv4, 0, sizeof(*outresult->socket->addripv4)); + + if (outresult->socket->addripv4 != NULL) + { + memcpy(&outresult->socket->addripv4->address, (struct sockaddr_in *)res->ai_addr, sizeof(struct sockaddr_in)); + + outresult->socket->isIPv6 = false; + char hoststr[NI_MAXHOST]; + char portstr[NI_MAXSERV]; + + socklen_t client_len = sizeof(struct sockaddr_storage); + getnameinfo((struct sockaddr *)&outresult->socket->addripv4->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + + TRACELOG(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr); + } + } + } break; + case AF_INET6: + { + outresult->socket->addripv6 = (struct _SocketAddressIPv6 *)RNET_MALLOC( + sizeof(*outresult->socket->addripv6)); + if (outresult->socket->addripv6 != NULL) + { + memset(outresult->socket->addripv6, 0, + sizeof(*outresult->socket->addripv6)); + if (outresult->socket->addripv6 != NULL) + { + memcpy(&outresult->socket->addripv6->address, + (struct sockaddr_in6 *)res->ai_addr, sizeof(struct sockaddr_in6)); + outresult->socket->isIPv6 = true; + char hoststr[NI_MAXHOST]; + char portstr[NI_MAXSERV]; + socklen_t client_len = sizeof(struct sockaddr_storage); + getnameinfo( + (struct sockaddr *)&outresult->socket->addripv6->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + TRACELOG(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr); + } + } + } break; + default: break; + } + } + + freeaddrinfo(res); + return success; +} + +// Set the state of the Socket sock to blocking +static bool SocketSetBlocking(Socket *sock) +{ + bool ret = true; +#if defined(_WIN32) + unsigned long mode = 0; + ret = ioctlsocket(sock->channel, FIONBIO, &mode); +#else + const int flags = fcntl(sock->channel, F_GETFL, 0); + if (!(flags & O_NONBLOCK)) + { + TRACELOG(LOG_DEBUG, "Socket was already in blocking mode"); + return ret; + } + + ret = (0 == fcntl(sock->channel, F_SETFL, (flags ^ O_NONBLOCK))); +#endif + return ret; +} + +// Set the state of the Socket sock to non-blocking +static bool SocketSetNonBlocking(Socket *sock) +{ + bool ret = true; +#if defined(_WIN32) + unsigned long mode = 1; + ret = ioctlsocket(sock->channel, FIONBIO, &mode); +#else + const int flags = fcntl(sock->channel, F_GETFL, 0); + + if ((flags & O_NONBLOCK)) + { + TRACELOG(LOG_DEBUG, "Socket was already in non-blocking mode"); + return ret; + } + + ret = (0 == fcntl(sock->channel, F_SETFL, (flags | O_NONBLOCK))); +#endif + return ret; +} + +// Set options specified in SocketConfig to Socket sock +static bool SocketSetOptions(SocketConfig *config, Socket *sock) +{ + for (int i = 0; i < SOCKET_MAX_SOCK_OPTS; i++) + { + SocketOpt *opt = &config->sockopts[i]; + + if (opt->id == 0) break; + + if (setsockopt(sock->channel, SOL_SOCKET, opt->id, opt->value, opt->valueLen) < 0) return false; + } + + return true; +} + +// Set "hints" in an addrinfo struct, to be passed to getaddrinfo. +static void SocketSetHints(SocketConfig *config, struct addrinfo *hints) +{ + if (config == NULL || hints == NULL) return; + + memset(hints, 0, sizeof(*hints)); + + // Check if the ip supplied in the config is a valid ipv4 ip ipv6 address + if (IsIPv4Address(config->host)) + { + hints->ai_family = AF_INET; + hints->ai_flags |= AI_NUMERICHOST; + } + else + { + if (IsIPv6Address(config->host)) + { + hints->ai_family = AF_INET6; + hints->ai_flags |= AI_NUMERICHOST; + } + else hints->ai_family = AF_UNSPEC; + } + + if (config->type == SOCKET_UDP) hints->ai_socktype = SOCK_DGRAM; + else hints->ai_socktype = SOCK_STREAM; + + + // Set passive unless UDP client + if (!(config->type == SOCKET_UDP) || config->server) hints->ai_flags = AI_PASSIVE; +} + +//---------------------------------------------------------------------------------- +// Module implementation +//---------------------------------------------------------------------------------- + +// Initialise the network (requires for windows platforms only) +bool InitNetworkDevice(void) +{ +#if defined(_WIN32) + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + int err = WSAStartup(wVersionRequested, &wsaData); + + if (err != 0) + { + TRACELOG(LOG_WARNING, "WinSock failed to initialise."); + return false; + } + else TRACELOG(LOG_INFO, "WinSock initialised."); + + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) + { + TRACELOG(LOG_WARNING, "WinSock failed to initialise."); + WSACleanup(); + return false; + } + + return true; +#else + return true; +#endif +} + +// Cleanup, and close the network +void CloseNetworkDevice(void) +{ +#if defined(_WIN32) + WSACleanup(); +#endif +} + +// Protocol-independent name resolution from an address to an ANSI host name +// and from a port number to the ANSI service name. +// +// The flags parameter can be used to customize processing of the getnameinfo function +// +// The following flags are available: +// +// NAME_INFO_DEFAULT 0x00 // No flags set +// NAME_INFO_NOFQDN 0x01 // Only return nodename portion for local hosts +// NAME_INFO_NUMERICHOST 0x02 // Return numeric form of the host's address +// NAME_INFO_NAMEREQD 0x04 // Error if the host's name not in DNS +// NAME_INFO_NUMERICSERV 0x08 // Return numeric form of the service (port #) +// NAME_INFO_DGRAM 0x10 // Service is a datagram service +void ResolveIP(const char *ip, const char *port, int flags, char *host, char *serv) +{ + // Variables + int status; // Status value to return (0) is success + struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?) + struct addrinfo *res; // A pointer to the resulting address list + + // Set the hints + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // Either IPv4 or IPv6 (AF_INET, AF_INET6) + hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP) + + // Populate address information + status = getaddrinfo(ip, // e.g. "www.example.com" or IP + port, // e.g. "http" or port number + &hints, // e.g. SOCK_STREAM/SOCK_DGRAM + &res // The struct to populate + ); + + // Did we succeed? + if (status != 0) TRACELOG(LOG_WARNING, "Failed to get resolve host %s:%s: %s", ip, port, gai_strerror(errno)); + else TRACELOG(LOG_DEBUG, "Resolving... %s::%s", ip, port); + + // Attempt to resolve network byte order ip to hostname + switch (res->ai_family) + { + case AF_INET: + { + status = getnameinfo(&*((struct sockaddr *)res->ai_addr), + sizeof(*((struct sockaddr_in *)res->ai_addr)), + host, NI_MAXHOST, serv, NI_MAXSERV, flags); + } break; + case AF_INET6: + { + /* + status = getnameinfo(&*((struct sockaddr_in6 *)res->ai_addr), // TODO. + sizeof(*((struct sockaddr_in6 *)res->ai_addr)), + host, NI_MAXHOST, serv, NI_MAXSERV, flags); + */ + } break; + default: break; + } + + if (status != 0) TRACELOG(LOG_WARNING, "Failed to resolve ip %s: %s", ip, SocketGetLastErrorString()); + else TRACELOG(LOG_DEBUG, "Successfully resolved %s::%s to %s", ip, port, host); + + // Free the pointer to the data returned by addrinfo + freeaddrinfo(res); +} + +// Protocol-independent translation from an ANSI host name to an address +// +// e.g. +// const char* address = "127.0.0.1" (local address) +// const char* port = "80" +// +// Parameters: +// const char* address - A pointer to a NULL-terminated ANSI string that contains a host (node) name or a numeric host address string. +// const char* service - A pointer to a NULL-terminated ANSI string that contains either a service name or port number represented as a string. +// +// Returns: +// The total amount of addresses found, -1 on error +// +int ResolveHost(const char *address, const char *service, int addressType, int flags, AddressInformation *outAddr) +{ + // Variables + int status; // Status value to return (0) is success + struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?) + struct addrinfo *res; // will point to the results + struct addrinfo *iterator; + assert(((address != NULL || address != 0) || (service != NULL || service != 0))); + assert(((addressType == AF_INET) || (addressType == AF_INET6) || (addressType == AF_UNSPEC))); + + // Set the hints + memset(&hints, 0, sizeof hints); + hints.ai_family = addressType; // Either IPv4 or IPv6 (ADDRESS_TYPE_IPV4, ADDRESS_TYPE_IPV6) + hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP) + hints.ai_flags = flags; + assert((hints.ai_addrlen == 0) || (hints.ai_addrlen == 0)); + assert((hints.ai_canonname == 0) || (hints.ai_canonname == 0)); + assert((hints.ai_addr == 0) || (hints.ai_addr == 0)); + assert((hints.ai_next == 0) || (hints.ai_next == 0)); + + // When the address is NULL, populate the IP for me + if (address == NULL) + { + if ((hints.ai_flags & AI_PASSIVE) == 0) hints.ai_flags |= AI_PASSIVE; + } + + TRACELOG(LOG_INFO, "Resolving host..."); + + // Populate address information + status = getaddrinfo(address, // e.g. "www.example.com" or IP + service, // e.g. "http" or port number + &hints, // e.g. SOCK_STREAM/SOCK_DGRAM + &res // The struct to populate + ); + + // Did we succeed? + if (status != 0) + { + int error = SocketGetLastError(); + SocketSetLastError(0); + TRACELOG(LOG_WARNING, "Failed to get resolve host: %s", SocketErrorCodeToString(error)); + return -1; + } + else TRACELOG(LOG_INFO, "Successfully resolved host %s:%s", address, service); + + // Calculate the size of the address information list + int size = 0; + for (iterator = res; iterator != NULL; iterator = iterator->ai_next) size++; + + // Validate the size is > 0, otherwise return + if (size <= 0) + { + TRACELOG(LOG_WARNING, "Error, no addresses found."); + return -1; + } + + // If not address list was allocated, allocate it dynamically with the known address size + if (outAddr == NULL) outAddr = (AddressInformation *)RNET_MALLOC(size * sizeof(AddressInformation)); + + // Dynamically allocate an array of address information structs + if (outAddr != NULL) + { + int i; + for (i = 0; i < size; ++i) + { + outAddr[i] = LoadAddress(); + if (outAddr[i] == NULL) + { + break; + } + } + + outAddr[i] = NULL; + if (i != size) outAddr = NULL; + } + else + { + TRACELOG(LOG_WARNING, "Error, failed to dynamically allocate memory for the address list"); + return -1; + } + + // Copy all the address information from res into outAddrList + int i = 0; + for (iterator = res; iterator != NULL; iterator = iterator->ai_next) + { + if (i < size) + { + outAddr[i]->addr.ai_flags = iterator->ai_flags; + outAddr[i]->addr.ai_family = iterator->ai_family; + outAddr[i]->addr.ai_socktype = iterator->ai_socktype; + outAddr[i]->addr.ai_protocol = iterator->ai_protocol; + outAddr[i]->addr.ai_addrlen = iterator->ai_addrlen; + *outAddr[i]->addr.ai_addr = *iterator->ai_addr; +#if NET_DEBUG_ENABLED + TRACELOG(LOG_DEBUG, "GetAddressInformation"); + TRACELOG(LOG_DEBUG, "\tFlags: 0x%x", iterator->ai_flags); + //PrintSocket(outAddr[i]->addr.ai_addr, outAddr[i]->addr.ai_family, outAddr[i]->addr.ai_socktype, outAddr[i]->addr.ai_protocol); + TRACELOG(LOG_DEBUG, "Length of this sockaddr: %d", outAddr[i]->addr.ai_addrlen); + TRACELOG(LOG_DEBUG, "Canonical name: %s", iterator->ai_canonname); +#endif + i++; + } + } + + // Free the pointer to the data returned by addrinfo + freeaddrinfo(res); + + // Return the total count of addresses found + return size; +} + +// This here is the bread and butter of the socket API, This function will +// attempt to open a socket, bind and listen to it based on the config passed in +// +// SocketConfig* config - Configuration for which socket to open +// SocketResult* result - The results of this function (if any, including errors) +// +// e.g. +// SocketConfig server_config = { SocketConfig client_config = { +// .host = "127.0.0.1", .host = "127.0.0.1", +// .port = 8080, .port = 8080, +// .server = true, }; +// .nonblocking = true, +// }; +// SocketResult server_res; SocketResult client_res; +bool SocketCreate(SocketConfig *config, SocketResult *result) +{ + // Socket creation result + bool success = true; + + // Make sure we've not received a null config or result pointer + if (config == NULL || result == NULL) return (success = false); + + // Set the defaults based on the config + if (!SocketSetDefaults(config)) + { + TRACELOG(LOG_WARNING, "Configuration Error."); + success = false; + } + else + { + // Create the socket + if (CreateSocket(config, result)) + { + if (config->nonblocking) SocketSetNonBlocking(result->socket); + else SocketSetBlocking(result->socket); + } + else success = false; + } + + return success; +} + +// Bind a socket to a local address +// Note: The bind function is required on an unconnected socket before subsequent calls to the listen function. +bool SocketBind(SocketConfig *config, SocketResult *result) +{ + bool success = false; + result->status = RESULT_FAILURE; + struct sockaddr_storage *sock_addr = NULL; + + // Don't bind to a socket that isn't configured as a server + if (!IsSocketValid(result->socket) || !config->server) + { + TRACELOG(LOG_WARNING, Cannot bind to socket marked as \"Client\" in SocketConfig."); + success = false; + } + else + { + if (result->socket->isIPv6) sock_addr = (struct sockaddr_storage *)&result->socket->addripv6->address; + else sock_addr = (struct sockaddr_storage *)&result->socket->addripv4->address; + + if (sock_addr != NULL) + { + if (bind(result->socket->channel, (struct sockaddr *)sock_addr, sizeof(*sock_addr)) != SOCKET_ERROR) + { + TRACELOG(LOG_INFO, "Successfully bound socket."); + success = true; + } + else + { + result->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + SocketSetLastError(0); + success = false; + } + } + } + + // Was the bind a success? + if (success) + { + result->status = RESULT_SUCCESS; + result->socket->ready = 0; + result->socket->status = 0; + socklen_t sock_len = sizeof(*sock_addr); + + if (getsockname(result->socket->channel, (struct sockaddr *)sock_addr, &sock_len) < 0) + { + TRACELOG(LOG_WARNING, "Couldn't get socket address"); + } + else + { + struct sockaddr_in *s = (struct sockaddr_in *)sock_addr; + // result->socket->address.host = s->sin_addr.s_addr; + // result->socket->address.port = s->sin_port; + + result->socket->addripv4 = (struct _SocketAddressIPv4 *)RNET_MALLOC(sizeof(*result->socket->addripv4)); + + if (result->socket->addripv4 != NULL) memset(result->socket->addripv4, 0, sizeof(*result->socket->addripv4)); + + memcpy(&result->socket->addripv4->address, (struct sockaddr_in *)&s->sin_addr, sizeof(struct sockaddr_in)); + } + } + return success; +} + +// Listens (and queues) incoming connections requests for a bound port. +bool SocketListen(SocketConfig *config, SocketResult *result) +{ + bool success = false; + result->status = RESULT_FAILURE; + + // Don't bind to a socket that isn't configured as a server + if (!IsSocketValid(result->socket) || !config->server) + { + TRACELOG(LOG_WARNING, "Cannot listen on socket marked as \"Client\" in SocketConfig."); + success = false; + } + else + { + // Don't listen on UDP sockets + if (!(config->type == SOCKET_UDP)) + { + if (listen(result->socket->channel, config->backlog_size) != SOCKET_ERROR) + { + TRACELOG(LOG_INFO, "Started listening on socket..."); + success = true; + } + else + { + success = false; + result->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + SocketSetLastError(0); + } + } + else + { + TRACELOG(LOG_WARNING, "Cannot listen on socket marked as \"UDP\" (datagram) in SocketConfig."); + success = false; + } + } + + // Was the listen a success? + if (success) + { + result->status = RESULT_SUCCESS; + result->socket->ready = 0; + result->socket->status = 0; + } + + return success; +} + +// Connect the socket to the destination specified by "host" and "port" in SocketConfig +bool SocketConnect(SocketConfig *config, SocketResult *result) +{ + bool success = true; + result->status = RESULT_FAILURE; + + // Only bind to sockets marked as server + if (config->server) + { + TRACELOG(LOG_WARNING, "Cannot connect to socket marked as \"Server\" in SocketConfig."); + success = false; + } + else + { + if (IsIPv4Address(config->host)) + { + struct sockaddr_in ip4addr; + ip4addr.sin_family = AF_INET; + unsigned long hport; + hport = strtoul(config->port, NULL, 0); + ip4addr.sin_port = (unsigned short)(hport); + + // TODO: Changed the code to avoid the usage of inet_pton and inet_ntop replacing them with getnameinfo (that should have a better support on windows). + + //inet_pton(AF_INET, config->host, &ip4addr.sin_addr); + int connect_result = connect(result->socket->channel, (struct sockaddr *)&ip4addr, sizeof(ip4addr)); + + if (connect_result == SOCKET_ERROR) + { + result->socket->status = SocketGetLastError(); + SocketSetLastError(0); + + switch (result->socket->status) + { + case WSAEWOULDBLOCK: success = true; break; + default: + { + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + success = false; + } break; + } + } + else + { + TRACELOG(LOG_INFO, "Successfully connected to socket."); + success = true; + } + } + else + { + if (IsIPv6Address(config->host)) + { + struct sockaddr_in6 ip6addr; + ip6addr.sin6_family = AF_INET6; + unsigned long hport; + hport = strtoul(config->port, NULL, 0); + ip6addr.sin6_port = htons((unsigned short)hport); + //inet_pton(AF_INET6, config->host, &ip6addr.sin6_addr); // TODO. + int connect_result = connect(result->socket->channel, (struct sockaddr *)&ip6addr, sizeof(ip6addr)); + + if (connect_result == SOCKET_ERROR) + { + result->socket->status = SocketGetLastError(); + SocketSetLastError(0); + + switch (result->socket->status) + { + case WSAEWOULDBLOCK: success = true; break; + default: + { + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + success = false; + } break; + } + } + else + { + TRACELOG(LOG_INFO, "Successfully connected to socket."); + success = true; + } + } + } + } + + if (success) + { + result->status = RESULT_SUCCESS; + result->socket->ready = 0; + result->socket->status = 0; + } + + return success; +} + +// Closes an existing socket +// +// SocketChannel socket - The id of the socket to close +void SocketClose(Socket *sock) +{ + if (sock != NULL) + { + if (sock->channel != INVALID_SOCKET) closesocket(sock->channel); + } +} + +// Returns the sockaddress for a specific socket in a generic storage struct +SocketAddressStorage SocketGetPeerAddress(Socket *sock) +{ + // TODO. + /* + if (sock->isServer) return NULL; + if (sock->isIPv6) return sock->addripv6; + else return sock->addripv4; + */ + + return NULL; +} + +// Return the address-type appropriate host portion of a socket address +const char *GetSocketAddressHost(SocketAddressStorage storage) +{ + assert(storage->address.ss_family == AF_INET || storage->address.ss_family == AF_INET6); + return SocketAddressToString((struct sockaddr_storage *)storage); +} + +// Return the address-type appropriate port(service) portion of a socket address +short GetSocketAddressPort(SocketAddressStorage storage) +{ + //return ntohs(GetSocketPortPtr(storage)); // TODO. + + return 0; +} + +// The accept function permits an incoming connection attempt on a socket. +// +// SocketChannel listener - The socket to listen for incoming connections on (i.e. server) +// SocketResult* out - The result of this function (if any, including errors) +// +// e.g. +// +// SocketResult connection; +// bool connected = false; +// if (!connected) +// { +// if (SocketAccept(server_res.socket.channel, &connection)) +// { +// connected = true; +// } +// } +Socket *SocketAccept(Socket *server, SocketConfig *config) +{ + if (!server->isServer || server->type == SOCKET_UDP) return NULL; + + struct sockaddr_storage sock_addr; + socklen_t sock_alen; + Socket *sock = LoadSocket(); + server->ready = 0; + sock_alen = sizeof(sock_addr); + sock->channel = accept(server->channel, (struct sockaddr *)&sock_addr, &sock_alen); + + if (sock->channel == INVALID_SOCKET) + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status)); + SocketSetLastError(0); + SocketClose(sock); + + return NULL; + } + + (config->nonblocking) ? SocketSetNonBlocking(sock) : SocketSetBlocking(sock); + sock->isServer = false; + sock->ready = 0; + sock->type = server->type; + + switch (sock_addr.ss_family) + { + case AF_INET: + { + struct sockaddr_in *s = ((struct sockaddr_in *)&sock_addr); + sock->addripv4 = (struct _SocketAddressIPv4 *)RNET_MALLOC(sizeof(*sock->addripv4)); + + if (sock->addripv4 != NULL) + { + memset(sock->addripv4, 0, sizeof(*sock->addripv4)); + memcpy(&sock->addripv4->address, (struct sockaddr_in *)&s->sin_addr, sizeof(struct sockaddr_in)); + TRACELOG(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *)s), ntohs(sock->addripv4->address.sin_port)); + } + } break; + case AF_INET6: + { + struct sockaddr_in6 *s = ((struct sockaddr_in6 *)&sock_addr); + sock->addripv6 = (struct _SocketAddressIPv6 *)RNET_MALLOC(sizeof(*sock->addripv6)); + + if (sock->addripv6 != NULL) + { + memset(sock->addripv6, 0, sizeof(*sock->addripv6)); + memcpy(&sock->addripv6->address, (struct sockaddr_in6 *)&s->sin6_addr, sizeof(struct sockaddr_in6)); + TRACELOG(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *)s), ntohs(sock->addripv6->address.sin6_port)); + } + } break; + } + + return sock; +} + +// Verify that the channel is in the valid range +static int ValidChannel(int channel) +{ + if ((channel < 0) || (channel >= SOCKET_MAX_UDPCHANNELS)) + { + TRACELOG(LOG_WARNING, "Invalid channel"); + return 0; + } + + return 1; +} + +// Set the socket channel +int SocketSetChannel(Socket *socket, int channel, const IPAddress *address) +{ + struct UDPChannel *binding; + + if (socket == NULL) + { + TRACELOG(LOG_WARNING, "Passed a NULL socket"); + return (-1); + } + + if (channel == -1) + { + for (channel = 0; channel < SOCKET_MAX_UDPCHANNELS; ++channel) + { + binding = &socket->binding[channel]; + if (binding->numbound < SOCKET_MAX_UDPADDRESSES) break; + } + } + else + { + if (!ValidChannel(channel)) return (-1); + + binding = &socket->binding[channel]; + } + + if (binding->numbound == SOCKET_MAX_UDPADDRESSES) + { + TRACELOG(LOG_WARNING, "No room for new addresses"); + return (-1); + } + + binding->address[binding->numbound++] = *address; + + return (channel); +} + +// Remove the socket channel +void SocketUnsetChannel(Socket *socket, int channel) +{ + if ((channel >= 0) && (channel < SOCKET_MAX_UDPCHANNELS)) socket->binding[channel].numbound = 0; +} + +/* Allocate/free a single UDP packet 'size' bytes long. + The new packet is returned, or NULL if the function ran out of memory. + */ +SocketDataPacket *AllocPacket(int size) +{ + SocketDataPacket *packet = (SocketDataPacket *)RNET_MALLOC(sizeof(*packet)); + int error = 1; + + if (packet != NULL) + { + packet->maxlen = size; + packet->data = (uint8_t *)RNET_MALLOC(size); + if (packet->data != NULL) + { + error = 0; + } + } + + if (error) + { + FreePacket(packet); + packet = NULL; + } + + return (packet); +} + +int ResizePacket(SocketDataPacket *packet, int newsize) +{ + uint8_t *newdata = (uint8_t *)RNET_MALLOC(newsize); + + if (newdata != NULL) + { + RNET_FREE(packet->data); + packet->data = newdata; + packet->maxlen = newsize; + } + + return (packet->maxlen); +} + +void FreePacket(SocketDataPacket *packet) +{ + if (packet) + { + RNET_FREE(packet->data); + RNET_FREE(packet); + } +} + +// Allocate/Free a UDP packet vector (array of packets) of 'howmany' packets, each 'size' bytes long. +// A pointer to the packet array is returned, or NULL if the function ran out of memory. +SocketDataPacket **AllocPacketList(int howmany, int size) +{ + SocketDataPacket **packetV = (SocketDataPacket **)RNET_MALLOC((howmany + 1) * sizeof(*packetV)); + + if (packetV != NULL) + { + int i; + for (i = 0; i < howmany; ++i) + { + packetV[i] = AllocPacket(size); + if (packetV[i] == NULL) + { + break; + } + } + packetV[i] = NULL; + + if (i != howmany) + { + FreePacketList(packetV); + packetV = NULL; + } + } + + return (packetV); +} + +void FreePacketList(SocketDataPacket **packetV) +{ + if (packetV) + { + for (int i = 0; packetV[i]; ++i) FreePacket(packetV[i]); + RNET_FREE(packetV); + } +} + +// Send 'len' bytes of 'data' over the non-server socket 'sock' +int SocketSend(Socket *sock, const void *datap, int length) +{ + int sent = 0; + int left = length; + int status = -1; + int numsent = 0; + const unsigned char *data = (const unsigned char *)datap; + + // Server sockets are for accepting connections only + if (sock->isServer) + { + TRACELOG(LOG_WARNING, "Cannot send information on a server socket"); + return -1; + } + + // Which socket are we trying to send data on + switch (sock->type) + { + case SOCKET_TCP: + { + SocketSetLastError(0); + do + { + length = send(sock->channel, (const char *)data, left, 0); + if (length > 0) + { + sent += length; + left -= length; + data += length; + } + } while ((left > 0) && // While we still have bytes left to send + ((length > 0) || // The amount of bytes we actually sent is > 0 + (SocketGetLastError() == WSAEINTR)) // The socket was interupted + ); + + if (length == SOCKET_ERROR) + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_DEBUG, "Socket Error: %s", SocketErrorCodeToString(sock->status)); + SocketSetLastError(0); + } + else TRACELOG(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, sent); + + return sent; + } break; + case SOCKET_UDP: + { + SocketSetLastError(0); + + if (sock->isIPv6) status = sendto(sock->channel, (const char *)data, left, 0, (struct sockaddr *)&sock->addripv6->address, sizeof(sock->addripv6->address)); + else status = sendto(sock->channel, (const char *)data, left, 0, (struct sockaddr *)&sock->addripv4->address, sizeof(sock->addripv4->address)); + + if (sent >= 0) + { + sock->status = 0; + ++numsent; + TRACELOG(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, status); + } + else + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_DEBUG, "Socket Error: %s", SocketGetLastErrorString(sock->status)); + SocketSetLastError(0); + return 0; + } + + return numsent; + } break; + default: break; + } + + return -1; +} + +// Receive up to 'maxlen' bytes of data over the non-server socket 'sock', +// and store them in the buffer pointed to by 'data'. +// This function returns the actual amount of data received. If the return +// value is less than or equal to zero, then either the remote connection was +// closed, or an unknown socket error occurred. +int SocketReceive(Socket *sock, void *data, int maxlen) +{ + int len = 0; + int numrecv = 0; + int status = 0; + socklen_t sock_len; + struct sockaddr_storage sock_addr; + //char ip[INET6_ADDRSTRLEN]; + + // Server sockets are for accepting connections only + if (sock->isServer && (sock->type == SOCKET_TCP)) + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_DEBUG, "Socket Error: %s", "Server sockets cannot be used to receive data"); + SocketSetLastError(0); + return 0; + } + + // Which socket are we trying to send data on + switch (sock->type) + { + case SOCKET_TCP: + { + SocketSetLastError(0); + do + { + len = recv(sock->channel, (char *)data, maxlen, 0); + } while (SocketGetLastError() == WSAEINTR); + + if (len > 0) + { + // Who sent the packet? + if (sock->type == SOCKET_UDP) + { + //TRACELOG(LOG_DEBUG, "Received data from: %s", inet_ntop(sock_addr.ss_family, GetSocketAddressPtr((struct sockaddr *)&sock_addr), ip, sizeof(ip))); + } + + ((unsigned char *)data)[len] = '\0'; // Add null terminating character to the end of the stream + TRACELOG(LOG_DEBUG, "Received \"%s\" (%d bytes)", data, len); + } + + sock->ready = 0; + return len; + } break; + case SOCKET_UDP: + { + SocketSetLastError(0); + sock_len = sizeof(sock_addr); + status = recvfrom(sock->channel, // The receving channel + data, // A pointer to the data buffer to fill + maxlen, // The max length of the data to fill + 0, // Flags + (struct sockaddr *)&sock_addr, // The address of the recevied data + &sock_len // The length of the received data address + ); + + if (status >= 0) ++numrecv; + else + { + sock->status = SocketGetLastError(); + + switch (sock->status) + { + case WSAEWOULDBLOCK: break; + default: TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status)); break; + } + + SocketSetLastError(0); + return 0; + } + + sock->ready = 0; + return numrecv; + } break; + default: break; + } + + return -1; +} + +// Does the socket have it's 'ready' flag set? +bool IsSocketReady(Socket *sock) +{ + return (sock != NULL) && (sock->ready); +} + +// Check if the socket is considered connected +bool IsSocketConnected(Socket *sock) +{ +#if defined(_WIN32) + FD_SET writefds; + FD_ZERO(&writefds); + FD_SET(sock->channel, &writefds); + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 1000000000UL; + int total = select(0, NULL, &writefds, NULL, &timeout); + + if (total == -1) + { // Error + sock->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status)); + SocketSetLastError(0); + } + else if (total == 0) return false; // Timeout + else if (FD_ISSET(sock->channel, &writefds)) return true; + + return false; +#else + return true; +#endif +} + +// Allocate and return a SocketResult struct +SocketResult *LoadSocketResult(void) +{ + struct SocketResult *res = (struct SocketResult *)RNET_MALLOC(sizeof(*res)); + + if (res != NULL) + { + memset(res, 0, sizeof(*res)); + if ((res->socket = LoadSocket()) == NULL) + { + RNET_FREE(res); + res = NULL; + } + } + + return res; +} + +// Free an allocated SocketResult +void UnloadSocketResult(SocketResult **result) +{ + if (*result != NULL) + { + if ((*result)->socket != NULL) UnloadSocket(&((*result)->socket)); + + RNET_FREE(*result); + *result = NULL; + } +} + +// Allocate a Socket +Socket *LoadSocket(void) +{ + struct Socket *sock; + sock = (Socket *)RNET_MALLOC(sizeof(*sock)); + + if (sock != NULL) memset(sock, 0, sizeof(*sock)); + else + { + TRACELOG(LOG_WARNING, "Ran out of memory attempting to allocate a socket"); + SocketClose(sock); + RNET_FREE(sock); + sock = NULL; + } + + return sock; +} + +// Free an allocated Socket +void UnloadSocket(Socket **sock) +{ + if (*sock != NULL) + { + RNET_FREE(*sock); + *sock = NULL; + } +} + +// Allocate a SocketSet +SocketSet *LoadSocketSet(int max) +{ + struct SocketSet *set = (struct SocketSet *)RNET_MALLOC(sizeof(*set)); + + if (set != NULL) + { + set->numsockets = 0; + set->maxsockets = max; + set->sockets = (struct Socket **)RNET_MALLOC(max * sizeof(*set->sockets)); + if (set->sockets != NULL) + { + for (int i = 0; i < max; ++i) set->sockets[i] = NULL; + } + else + { + RNET_FREE(set); + set = NULL; + } + } + + return (set); +} + +// Free an allocated SocketSet +void UnloadSocketSet(SocketSet *set) +{ + if (set) + { + RNET_FREE(set->sockets); + RNET_FREE(set); + } +} + +// Add a Socket "sock" to the SocketSet "set" +int AddSocket(SocketSet *set, Socket *sock) +{ + if (sock != NULL) + { + if (set->numsockets == set->maxsockets) + { + TRACELOG(LOG_DEBUG, "Socket Error: %s", "SocketSet is full"); + SocketSetLastError(0); + return (-1); + } + set->sockets[set->numsockets++] = (struct Socket *)sock; + } + else + { + TRACELOG(LOG_DEBUG, "Socket Error: %s", "Socket was null"); + SocketSetLastError(0); + return (-1); + } + + return (set->numsockets); +} + +// Remove a Socket "sock" to the SocketSet "set" +int RemoveSocket(SocketSet *set, Socket *sock) +{ + if (sock != NULL) + { + int i = 0; + for (i = 0; i < set->numsockets; ++i) + { + if (set->sockets[i] == (struct Socket *)sock) break; + } + + if (i == set->numsockets) + { + TRACELOG(LOG_DEBUG, "Socket Error: %s", "Socket not found"); + SocketSetLastError(0); + return (-1); + } + + --set->numsockets; + for (; i < set->numsockets; ++i) set->sockets[i] = set->sockets[i + 1]; + } + + return (set->numsockets); +} + +// Check the sockets in the socket set for pending information +int CheckSockets(SocketSet *set, unsigned int timeout) +{ + int i; + SOCKET maxfd; + int retval; + struct timeval tv; + fd_set mask; + + /* Find the largest file descriptor */ + maxfd = 0; + for (i = set->numsockets - 1; i >= 0; --i) + { + if (set->sockets[i]->channel > maxfd) + { + maxfd = set->sockets[i]->channel; + } + } + + // Check the file descriptors for available data + do + { + SocketSetLastError(0); + + // Set up the mask of file descriptors + FD_ZERO(&mask); + for (i = set->numsockets - 1; i >= 0; --i) + { + FD_SET(set->sockets[i]->channel, &mask); + } // Set up the timeout + + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + /* Look! */ + retval = select(maxfd + 1, &mask, NULL, NULL, &tv); + } while (SocketGetLastError() == WSAEINTR); + + // Mark all file descriptors ready that have data available + if (retval > 0) + { + for (i = set->numsockets - 1; i >= 0; --i) + { + if (FD_ISSET(set->sockets[i]->channel, &mask)) set->sockets[i]->ready = 1; + } + } + + return retval; +} + +// Allocate an AddressInformation +AddressInformation LoadAddress(void) +{ + AddressInformation addressInfo = NULL; + addressInfo = (AddressInformation)RNET_CALLOC(1, sizeof(*addressInfo)); + + if (addressInfo != NULL) + { + addressInfo->addr.ai_addr = (struct sockaddr *)RNET_CALLOC(1, sizeof(struct sockaddr)); + if (addressInfo->addr.ai_addr == NULL) TRACELOG(LOG_WARNING, "Failed to allocate memory for \"struct sockaddr\""); + } + else TRACELOG(LOG_WARNING, "Failed to allocate memory for \"struct AddressInformation\""); + + return addressInfo; +} + +// Free an AddressInformation struct +void UnloadAddress(AddressInformation *addressInfo) +{ + if (*addressInfo != NULL) + { + if ((*addressInfo)->addr.ai_addr != NULL) + { + RNET_FREE((*addressInfo)->addr.ai_addr); + (*addressInfo)->addr.ai_addr = NULL; + } + + RNET_FREE(*addressInfo); + *addressInfo = NULL; + } +} + +// Allocate a list of AddressInformation +AddressInformation *LoadAddressList(int size) +{ + AddressInformation *addr; + addr = (AddressInformation *)RNET_MALLOC(size * sizeof(AddressInformation)); + return addr; +} + +// Opaque datatype accessor addrinfo->ai_family +int GetAddressFamily(AddressInformation address) +{ + return address->addr.ai_family; +} + +// Opaque datatype accessor addrinfo->ai_socktype +int GetAddressSocketType(AddressInformation address) +{ + return address->addr.ai_socktype; +} + +// Opaque datatype accessor addrinfo->ai_protocol +int GetAddressProtocol(AddressInformation address) +{ + return address->addr.ai_protocol; +} + +// Opaque datatype accessor addrinfo->ai_canonname +char *GetAddressCanonName(AddressInformation address) +{ + return address->addr.ai_canonname; +} + +// Opaque datatype accessor addrinfo->ai_addr +char *GetAddressHostAndPort(AddressInformation address, char *outhost, unsigned short *outport) +{ + //char *ip[INET6_ADDRSTRLEN]; + char *result = NULL; + struct sockaddr_storage *storage = (struct sockaddr_storage *)address->addr.ai_addr; + + switch (storage->ss_family) + { + case AF_INET: + { + struct sockaddr_in *s = ((struct sockaddr_in *)address->addr.ai_addr); + //result = inet_ntop(AF_INET, &s->sin_addr, ip, INET_ADDRSTRLEN); // TODO. + *outport = ntohs(s->sin_port); + } break; + case AF_INET6: + { + struct sockaddr_in6 *s = ((struct sockaddr_in6 *)address->addr.ai_addr); + //result = inet_ntop(AF_INET6, &s->sin6_addr, ip, INET6_ADDRSTRLEN); // TODO. + *outport = ntohs(s->sin6_port); + } break; + default: break; + } + + if (result == NULL) + { + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(SocketGetLastError())); + SocketSetLastError(0); + } + else + { + strcpy(outhost, result); + } + return result; +} + +// +void PacketSend(Packet *packet) +{ + TRACELOG(LOG_INFO, "Sending packet (%s) with size %d\n", packet->data, packet->size); +} + +// +void PacketReceive(Packet *packet) +{ + TRACELOG(LOG_INFO, "Receiving packet (%s) with size %d\n", packet->data, packet->size); +} + +// +void PacketWrite16(Packet *packet, uint16_t value) +{ + TRACELOG(LOG_INFO, "Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value); + uint8_t *data = packet->data + packet->offs; + *data++ = (uint8_t)(value >> 8); + *data++ = (uint8_t)(value); + packet->size += sizeof(uint16_t); + packet->offs += sizeof(uint16_t); + TRACELOG(LOG_INFO, "Network: 0x%04" PRIX16 " - %" PRIu16 "\n", (uint16_t) *data, (uint16_t) *data); +} + +// +void PacketWrite32(Packet *packet, uint32_t value) +{ + TRACELOG(LOG_INFO, "Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value); + uint8_t *data = packet->data + packet->offs; + *data++ = (uint8_t)(value >> 24); + *data++ = (uint8_t)(value >> 16); + *data++ = (uint8_t)(value >> 8); + *data++ = (uint8_t)(value); + packet->size += sizeof(uint32_t); + packet->offs += sizeof(uint32_t); + + TRACELOG(LOG_INFO, "Network: 0x%08" PRIX32 " - %" PRIu32 "\n", + (uint32_t)(((intptr_t) packet->data) - packet->offs), + (uint32_t)(((intptr_t) packet->data) - packet->offs)); +} + +// +void PacketWrite64(Packet *packet, uint64_t value) +{ + TRACELOG(LOG_INFO, "Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value); + + uint8_t *data = packet->data + packet->offs; + *data++ = (uint8_t)(value >> 56); + *data++ = (uint8_t)(value >> 48); + *data++ = (uint8_t)(value >> 40); + *data++ = (uint8_t)(value >> 32); + *data++ = (uint8_t)(value >> 24); + *data++ = (uint8_t)(value >> 16); + *data++ = (uint8_t)(value >> 8); + *data++ = (uint8_t)(value); + packet->size += sizeof(uint64_t); + packet->offs += sizeof(uint64_t); + + TRACELOG(LOG_INFO, "Network: 0x%016" PRIX64 " - %" PRIu64 "\n", (uint64_t)(packet->data - packet->offs), (uint64_t)(packet->data - packet->offs)); +} + +// +uint16_t PacketRead16(Packet *packet) +{ + uint8_t *data = packet->data + packet->offs; + packet->size += sizeof(uint16_t); + packet->offs += sizeof(uint16_t); + uint16_t value = ((uint16_t) data[0] << 8) | data[1]; + TRACELOG(LOG_INFO, "Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value); + + return value; +} + +// +uint32_t PacketRead32(Packet *packet) +{ + uint8_t *data = packet->data + packet->offs; + packet->size += sizeof(uint32_t); + packet->offs += sizeof(uint32_t); + uint32_t value = ((uint32_t) data[0] << 24) | ((uint32_t) data[1] << 16) | ((uint32_t) data[2] << 8) | data[3]; + TRACELOG(LOG_INFO, "Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value); + + return value; +} + +// +uint64_t PacketRead64(Packet *packet) +{ + uint8_t *data = packet->data + packet->offs; + packet->size += sizeof(uint64_t); + packet->offs += sizeof(uint64_t); + uint64_t value = ((uint64_t) data[0] << 56) | ((uint64_t) data[1] << 48) | ((uint64_t) data[2] << 40) | ((uint64_t) data[3] << 32) | ((uint64_t) data[4] << 24) | ((uint64_t) data[5] << 16) | ((uint64_t) data[6] << 8) | data[7]; + TRACELOG(LOG_INFO, "Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value); + + return value; +} + +#endif // RNET_IMPLEMENTATION \ No newline at end of file diff --git a/raylib/shapes.c b/raylib/shapes.c new file mode 100644 index 0000000..ae26111 --- /dev/null +++ b/raylib/shapes.c @@ -0,0 +1,1662 @@ +/********************************************************************************************** +* +* raylib.shapes - Basic functions to draw 2d Shapes and check collisions +* +* CONFIGURATION: +* +* #define SUPPORT_QUADS_DRAW_MODE +* Use QUADS instead of TRIANGLES for drawing when possible. +* Some lines-based shapes could still use lines +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 + +#include // Required for: sinf(), asinf(), cosf(), acosf(), sqrtf(), fabsf() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Error rate to calculate how many segments we need to draw a smooth circle, +// taken from https://stackoverflow.com/a/2244088 +#ifndef SMOOTH_CIRCLE_ERROR_RATE + #define SMOOTH_CIRCLE_ERROR_RATE 0.5f +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Not here... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (usually a white pixel) +Rectangle texShapesRec = { 0, 0, 1, 1 }; // Texture source rectangle used on shapes drawing + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static float EaseCubicInOut(float t, float b, float c, float d); // Cubic easing + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +void SetShapesTexture(Texture2D texture, Rectangle source) +{ + texShapes = texture; + texShapesRec = source; +} + +// Draw a pixel +void DrawPixel(int posX, int posY, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2i(posX, posY); + rlVertex2i(posX + 1, posY + 1); + rlEnd(); +} + +// Draw a pixel (Vector version) +void DrawPixelV(Vector2 position, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(position.x, position.y); + rlVertex2f(position.x + 1.0f, position.y + 1.0f); + rlEnd(); +} + +// Draw a line +void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2i(startPosX, startPosY); + rlVertex2i(endPosX, endPosY); + rlEnd(); +} + +// Draw a line (Vector version) +void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(startPos.x, startPos.y); + rlVertex2f(endPos.x, endPos.y); + rlEnd(); +} + +// Draw a line defining thickness +void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) +{ + Vector2 delta = {endPos.x-startPos.x, endPos.y-startPos.y}; + float length = sqrtf(delta.x*delta.x + delta.y*delta.y); + + if (length > 0 && thick > 0) + { + float scale = thick/(2*length); + Vector2 radius = {-scale*delta.y, scale*delta.x}; + Vector2 strip[] = {{startPos.x-radius.x, startPos.y-radius.y}, {startPos.x+radius.x, startPos.y+radius.y}, + {endPos.x-radius.x, endPos.y-radius.y}, {endPos.x+radius.x, endPos.y+radius.y}}; + + DrawTriangleStrip(strip, 4, color); + } +} + +// Draw line using cubic-bezier curves in-out +void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) +{ +#ifndef BEZIER_LINE_DIVISIONS + #define BEZIER_LINE_DIVISIONS 24 // Bezier line divisions +#endif + + Vector2 previous = startPos; + Vector2 current; + + for (int i = 1; i <= BEZIER_LINE_DIVISIONS; i++) + { + // Cubic easing in-out + // NOTE: Easing is calculated only for y position value + current.y = EaseCubicInOut((float)i, startPos.y, endPos.y - startPos.y, (float)BEZIER_LINE_DIVISIONS); + current.x = previous.x + (endPos.x - startPos.x)/ (float)BEZIER_LINE_DIVISIONS; + + DrawLineEx(previous, current, thick, color); + + previous = current; + } +} + +// Draw line using quadratic bezier curves with a control point +void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color) +{ + const float step = 1.0f/BEZIER_LINE_DIVISIONS; + + Vector2 previous = startPos; + Vector2 current = { 0 }; + float t = 0.0f; + + for (int i = 0; i <= BEZIER_LINE_DIVISIONS; i++) + { + t = step*i; + float a = powf(1 - t, 2); + float b = 2*(1 - t)*t; + float c = powf(t, 2); + + // NOTE: The easing functions aren't suitable here because they don't take a control point + current.y = a*startPos.y + b*controlPos.y + c*endPos.y; + current.x = a*startPos.x + b*controlPos.x + c*endPos.x; + + DrawLineEx(previous, current, thick, color); + + previous = current; + } +} + +// Draw lines sequence +void DrawLineStrip(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 2) + { + rlCheckRenderBatchLimit(pointsCount); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < pointsCount - 1; i++) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); + } +} + +// Draw a color-filled circle +void DrawCircle(int centerX, int centerY, float radius, Color color) +{ + DrawCircleV((Vector2){ (float)centerX, (float)centerY }, radius, color); +} + +// Draw a piece of a circle +void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*segments/2); + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + // NOTE: Every QUAD actually represents two segments + for (int i = 0; i < segments/2; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength*2))*radius, center.y + cosf(DEG2RAD*(angle + stepLength*2))*radius); + + angle += (stepLength*2); + } + + // NOTE: In case number of segments is odd, we add one last piece to the cake + if (segments%2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + rlEnd(); + + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(3*segments); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + // Hide the cap lines when the circle is full + bool showCapLines = true; + int limit = 2*(segments + 2); + if ((int)(endAngle - startAngle)%360 == 0) { limit = 2*segments; showCapLines = false; } + + rlCheckRenderBatchLimit(limit); + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + } + rlEnd(); +} + +// Draw a gradient-filled circle +// NOTE: Gradient goes from center (color1) to border (color2) +void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) +{ + rlCheckRenderBatchLimit(3*36); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color1.r, color1.g, color1.b, color1.a); + rlVertex2f((float)centerX, (float)centerY); + rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlVertex2f((float)centerX + sinf(DEG2RAD*i)*radius, (float)centerY + cosf(DEG2RAD*i)*radius); + rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlVertex2f((float)centerX + sinf(DEG2RAD*(i + 10))*radius, (float)centerY + cosf(DEG2RAD*(i + 10))*radius); + } + rlEnd(); +} + +// Draw a color-filled circle (Vector version) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +void DrawCircleV(Vector2 center, float radius, Color color) +{ + DrawCircleSector(center, radius, 0, 360, 36, color); +} + +// Draw circle outline +void DrawCircleLines(int centerX, int centerY, float radius, Color color) +{ + rlCheckRenderBatchLimit(2*36); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) + for (int i = 0; i < 360; i += 10) + { + rlVertex2f(centerX + sinf(DEG2RAD*i)*radius, centerY + cosf(DEG2RAD*i)*radius); + rlVertex2f(centerX + sinf(DEG2RAD*(i + 10))*radius, centerY + cosf(DEG2RAD*(i + 10))*radius); + } + rlEnd(); +} + +// Draw ellipse +void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + rlCheckRenderBatchLimit(3*36); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f((float)centerX, (float)centerY); + rlVertex2f((float)centerX + sinf(DEG2RAD*i)*radiusH, (float)centerY + cosf(DEG2RAD*i)*radiusV); + rlVertex2f((float)centerX + sinf(DEG2RAD*(i + 10))*radiusH, (float)centerY + cosf(DEG2RAD*(i + 10))*radiusV); + } + rlEnd(); +} + +// Draw ellipse outline +void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + rlCheckRenderBatchLimit(2*36); + + rlBegin(RL_LINES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(centerX + sinf(DEG2RAD*i)*radiusH, centerY + cosf(DEG2RAD*i)*radiusV); + rlVertex2f(centerX + sinf(DEG2RAD*(i + 10))*radiusH, centerY + cosf(DEG2RAD*(i + 10))*radiusV); + } + rlEnd(); +} + +void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + // Not a ring + if (innerRadius <= 0.0f) + { + DrawCircleSector(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*segments); + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + rlEnd(); + + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(6*segments); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + if (innerRadius <= 0.0f) + { + DrawCircleSectorLines(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + bool showCapLines = true; + int limit = 4*(segments + 1); + if ((int)(endAngle - startAngle)%360 == 0) { limit = 4*segments; showCapLines = false; } + + rlCheckRenderBatchLimit(limit); + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + } + rlEnd(); +} + +// Draw a color-filled rectangle +void DrawRectangle(int posX, int posY, int width, int height, Color color) +{ + DrawRectangleV((Vector2){ (float)posX, (float)posY }, (Vector2){ (float)width, (float)height }, color); +} + +// Draw a color-filled rectangle (Vector version) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +void DrawRectangleV(Vector2 position, Vector2 size, Color color) +{ + DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle +void DrawRectangleRec(Rectangle rec, Color color) +{ + DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle with pro parameters +void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) +{ + rlCheckRenderBatchLimit(4); + + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = rec.x - origin.x; + float y = rec.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + rec.width, y }; + bottomLeft = (Vector2){ x, y + rec.height }; + bottomRight = (Vector2){ x + rec.width, y + rec.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = rec.x; + float y = rec.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + rec.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + rec.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + rec.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + rec.height)*cosRotation; + + bottomRight.x = x + (dx + rec.width)*cosRotation - (dy + rec.height)*sinRotation; + bottomRight.y = y + (dx + rec.width)*sinRotation + (dy + rec.height)*cosRotation; + } + + rlSetTexture(texShapes.id); + rlBegin(RL_QUADS); + + rlNormal3f(0.0f, 0.0f, 1.0f); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(topLeft.x, topLeft.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(bottomRight.x, bottomRight.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + rlSetTexture(0); +} + +// Draw a vertical-gradient-filled rectangle +// NOTE: Gradient goes from bottom (color1) to top (color2) +void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color2, color2, color1); +} + +// Draw a horizontal-gradient-filled rectangle +// NOTE: Gradient goes from bottom (color1) to top (color2) +void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2) +{ + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color1, color2, color2); +} + +// Draw a gradient-filled rectangle +// NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise +void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) +{ + rlSetTexture(texShapes.id); + + rlPushMatrix(); + rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); + + // NOTE: Default raylib font character 95 is a white square + rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(rec.x, rec.y); + + rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(rec.x, rec.y + rec.height); + + rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); + + rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y); + rlEnd(); + rlPopMatrix(); + + rlSetTexture(0); +} + +// Draw rectangle outline +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +void DrawRectangleLines(int posX, int posY, int width, int height, Color color) +{ +#if defined(SUPPORT_QUADS_DRAW_MODE) + DrawRectangle(posX, posY, width, 1, color); + DrawRectangle(posX + width - 1, posY + 1, 1, height - 2, color); + DrawRectangle(posX, posY + height - 1, width, 1, color); + DrawRectangle(posX, posY + 1, 1, height - 2, color); +#else + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2i(posX + 1, posY + 1); + rlVertex2i(posX + width, posY + 1); + + rlVertex2i(posX + width, posY + 1); + rlVertex2i(posX + width, posY + height); + + rlVertex2i(posX + width, posY + height); + rlVertex2i(posX + 1, posY + height); + + rlVertex2i(posX + 1, posY + height); + rlVertex2i(posX + 1, posY + 1); + rlEnd(); +#endif +} + +// Draw rectangle outline with extended parameters +void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) +{ + if ((lineThick > rec.width) || (lineThick > rec.height)) + { + if (rec.width > rec.height) lineThick = (int)rec.height/2; + else if (rec.width < rec.height) lineThick = (int)rec.width/2; + } + + // When rec = { x, y, 8.0f, 6.0f } and lineThick = 2, the following + // four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight): + // + // TTTTTTTT + // TTTTTTTT + // LL RR + // LL RR + // BBBBBBBB + // BBBBBBBB + // + float thick = (float)lineThick; + Rectangle top = { rec.x, rec.y, rec.width, thick }; + Rectangle bottom = { rec.x, rec.y - thick + rec.height, rec.width, thick }; + Rectangle left = { rec.x, rec.y + thick, thick, rec.height - thick*2.0f }; + Rectangle right = { rec.x - thick + rec.width, rec.y + thick, thick, rec.height - thick*2.0f }; + + DrawRectangleRec(top, color); + DrawRectangleRec(bottom, color); + DrawRectangleRec(left, color); + DrawRectangleRec(right, color); +} + +// Draw rectangle with rounded edges +void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color) +{ + // Not a rounded rectangle + if ((roundness <= 0.0f) || (rec.width < 1) || (rec.height < 1 )) + { + DrawRectangleRec(rec, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)(ceilf(2*PI/th)/4.0f); + if (segments <= 0) segments = 4; + } + + float stepLength = 90.0f/(float)segments; + + /* + Quick sketch to make sense of all of this, + there are 9 parts to draw, also mark the 12 points we'll use + + P0____________________P1 + /| |\ + /1| 2 |3\ + P7 /__|____________________|__\ P2 + | |P8 P9| | + | 8 | 9 | 4 | + | __|____________________|__ | + P6 \ |P11 P10| / P3 + \7| 6 |5/ + \|____________________|/ + P5 P4 + */ + // Coordinates of the 12 points that define the rounded rect + const Vector2 point[12] = { + {(float)rec.x + radius, rec.y}, {(float)(rec.x + rec.width) - radius, rec.y}, { rec.x + rec.width, (float)rec.y + radius }, // PO, P1, P2 + {rec.x + rec.width, (float)(rec.y + rec.height) - radius}, {(float)(rec.x + rec.width) - radius, rec.y + rec.height}, // P3, P4 + {(float)rec.x + radius, rec.y + rec.height}, { rec.x, (float)(rec.y + rec.height) - radius}, {rec.x, (float)rec.y + radius}, // P5, P6, P7 + {(float)rec.x + radius, (float)rec.y + radius}, {(float)(rec.x + rec.width) - radius, (float)rec.y + radius}, // P8, P9 + {(float)(rec.x + rec.width) - radius, (float)(rec.y + rec.height) - radius}, {(float)rec.x + radius, (float)(rec.y + rec.height) - radius} // P10, P11 + }; + + const Vector2 centers[4] = { point[8], point[9], point[10], point[11] }; + const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(16*segments/2 + 5*4); + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + // NOTE: Every QUAD actually represents two segments + for (int i = 0; i < segments/2; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength*2))*radius, center.y + cosf(DEG2RAD*(angle + stepLength*2))*radius); + angle += (stepLength*2); + } + + // NOTE: In case number of segments is odd, we add one last piece to the cake + if (segments%2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + + rlEnd(); + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(12*segments + 5*6); // 4 corners with 3 vertices per segment + 5 rectangles with 6 vertices each + + rlBegin(RL_TRIANGLES); + + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + angle += stepLength; + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[4].x, point[4].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[11].x, point[11].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[10].x, point[10].y); + rlEnd(); +#endif +} + +// Draw rectangle with rounded edges outline +void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color) +{ + if (lineThick < 0) lineThick = 0; + + // Not a rounded rectangle + if (roundness <= 0.0f) + { + DrawRectangleLinesEx((Rectangle){rec.x-lineThick, rec.y-lineThick, rec.width+2*lineThick, rec.height+2*lineThick}, lineThick, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)(ceilf(2*PI/th)/2.0f); + if (segments <= 0) segments = 4; + } + + float stepLength = 90.0f/(float)segments; + const float outerRadius = radius + (float)lineThick, innerRadius = radius; + + /* + Quick sketch to make sense of all of this, + marks the 16 + 4(corner centers P16-19) points we'll use + + P0 ================== P1 + // P8 P9 \\ + // \\ + P7 // P15 P10 \\ P2 + || *P16 P17* || + || || + || P14 P11 || + P6 \\ *P19 P18* // P3 + \\ // + \\ P13 P12 // + P5 ================== P4 + */ + const Vector2 point[16] = { + {(float)rec.x + innerRadius, rec.y - lineThick}, {(float)(rec.x + rec.width) - innerRadius, rec.y - lineThick}, { rec.x + rec.width + lineThick, (float)rec.y + innerRadius }, // PO, P1, P2 + {rec.x + rec.width + lineThick, (float)(rec.y + rec.height) - innerRadius}, {(float)(rec.x + rec.width) - innerRadius, rec.y + rec.height + lineThick}, // P3, P4 + {(float)rec.x + innerRadius, rec.y + rec.height + lineThick}, { rec.x - lineThick, (float)(rec.y + rec.height) - innerRadius}, {rec.x - lineThick, (float)rec.y + innerRadius}, // P5, P6, P7 + {(float)rec.x + innerRadius, rec.y}, {(float)(rec.x + rec.width) - innerRadius, rec.y}, // P8, P9 + { rec.x + rec.width, (float)rec.y + innerRadius }, {rec.x + rec.width, (float)(rec.y + rec.height) - innerRadius}, // P10, P11 + {(float)(rec.x + rec.width) - innerRadius, rec.y + rec.height}, {(float)rec.x + innerRadius, rec.y + rec.height}, // P12, P13 + { rec.x, (float)(rec.y + rec.height) - innerRadius}, {rec.x, (float)rec.y + innerRadius} // P14, P15 + }; + + const Vector2 centers[4] = { + {(float)rec.x + innerRadius, (float)rec.y + innerRadius}, {(float)(rec.x + rec.width) - innerRadius, (float)rec.y + innerRadius}, // P16, P17 + {(float)(rec.x + rec.width) - innerRadius, (float)(rec.y + rec.height) - innerRadius}, {(float)rec.x + innerRadius, (float)(rec.y + rec.height) - innerRadius} // P18, P19 + }; + + const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; + + if (lineThick > 1) + { +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*4*segments + 4*4); // 4 corners with 4 vertices for each segment + 4 rectangles with 4 vertices each + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[13].x, point[13].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[12].x, point[12].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[15].x, point[15].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[14].x, point[14].y); + + rlEnd(); + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(4*6*segments + 4*6); // 4 corners with 6(2*3) vertices for each segment + 4 rectangles with 6 vertices each + + rlBegin(RL_TRIANGLES); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[12].x, point[12].y); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[4].x, point[4].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[14].x, point[14].y); + rlVertex2f(point[15].x, point[15].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[14].x, point[14].y); + rlEnd(); +#endif + } + else + { + // Use LINES to draw the outline + rlCheckRenderBatchLimit(8*segments + 4*2); // 4 corners with 2 vertices for each segment + 4 rectangles with 2 vertices each + + rlBegin(RL_LINES); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + angle += stepLength; + } + } + + // And now the remaining 4 lines + for (int i = 0; i < 8; i += 2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[i].x, point[i].y); + rlVertex2f(point[i + 1].x, point[i + 1].y); + } + + rlEnd(); + } +} + +// Draw a triangle +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + rlCheckRenderBatchLimit(4); + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(v1.x, v1.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(v3.x, v3.y); + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + rlEnd(); +#endif +} + +// Draw a triangle using lines +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + rlCheckRenderBatchLimit(6); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + + rlVertex2f(v3.x, v3.y); + rlVertex2f(v1.x, v1.y); + rlEnd(); +} + +// Draw a triangle fan defined by points +// NOTE: First vertex provided is the center, shared by all triangles +// By default, following vertex should be provided in counter-clockwise order +void DrawTriangleFan(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + rlCheckRenderBatchLimit((pointsCount - 2)*4); + + rlSetTexture(texShapes.id); + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 1; i < pointsCount - 1; i++) + { + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(points[0].x, points[0].y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(points[i].x, points[i].y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); + rlSetTexture(0); + } +} + +// Draw a triangle strip defined by points +// NOTE: Every new vertex connects with previous two +void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + rlCheckRenderBatchLimit(3*(pointsCount - 2)); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 2; i < pointsCount; i++) + { + if ((i%2) == 0) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + } + else + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + } + } + rlEnd(); + } +} + +// Draw a regular polygon of n sides (Vector version) +void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = 0.0f; + + rlCheckRenderBatchLimit(4*(360/sides)); + + rlPushMatrix(); + rlTranslatef(center.x, center.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(0, 0); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + centralAngle += 360.0f/(float)sides; + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + } + rlEnd(); + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(0, 0); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + centralAngle += 360.0f/(float)sides; + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + } + rlEnd(); +#endif + rlPopMatrix(); +} + +// Draw a polygon outline of n sides +void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = 0.0f; + + rlCheckRenderBatchLimit(3*(360/sides)); + + rlPushMatrix(); + rlTranslatef(center.x, center.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + + rlBegin(RL_LINES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + centralAngle += 360.0f/(float)sides; + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + } + rlEnd(); + rlPopMatrix(); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Collision Detection functions +//---------------------------------------------------------------------------------- + +// Check if point is inside rectangle +bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Check if point is inside circle +bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius) +{ + return CheckCollisionCircles(point, 0, center, radius); +} + +// Check if point is inside a triangle defined by three points (p1, p2, p3) +bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3) +{ + bool collision = false; + + float alpha = ((p2.y - p3.y)*(point.x - p3.x) + (p3.x - p2.x)*(point.y - p3.y)) / + ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y)); + + float beta = ((p3.y - p1.y)*(point.x - p3.x) + (p1.x - p3.x)*(point.y - p3.y)) / + ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0) && (beta > 0) && (gamma > 0)) collision = true; + + return collision; +} + +// Check collision between two rectangles +bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) +{ + bool collision = false; + + if ((rec1.x < (rec2.x + rec2.width) && (rec1.x + rec1.width) > rec2.x) && + (rec1.y < (rec2.y + rec2.height) && (rec1.y + rec1.height) > rec2.y)) collision = true; + + return collision; +} + +// Check collision between two circles +bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2) +{ + bool collision = false; + + float dx = center2.x - center1.x; // X distance between centers + float dy = center2.y - center1.y; // Y distance between centers + + float distance = sqrtf(dx*dx + dy*dy); // Distance between centers + + if (distance <= (radius1 + radius2)) collision = true; + + return collision; +} + +// Check collision between circle and rectangle +// NOTE: Reviewed version to take into account corner limit case +bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) +{ + int recCenterX = (int)(rec.x + rec.width/2.0f); + int recCenterY = (int)(rec.y + rec.height/2.0f); + + float dx = fabsf(center.x - (float)recCenterX); + float dy = fabsf(center.y - (float)recCenterY); + + if (dx > (rec.width/2.0f + radius)) { return false; } + if (dy > (rec.height/2.0f + radius)) { return false; } + + if (dx <= (rec.width/2.0f)) { return true; } + if (dy <= (rec.height/2.0f)) { return true; } + + float cornerDistanceSq = (dx - rec.width/2.0f)*(dx - rec.width/2.0f) + + (dy - rec.height/2.0f)*(dy - rec.height/2.0f); + + return (cornerDistanceSq <= (radius*radius)); +} + +// Check the collision between two lines defined by two points each, returns collision point by reference +bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint) +{ + const float div = (endPos2.y - startPos2.y)*(endPos1.x - startPos1.x) - (endPos2.x - startPos2.x)*(endPos1.y - startPos1.y); + + if (div == 0.0f) return false; // WARNING: This check could not work due to float precision rounding issues... + + const float xi = ((startPos2.x - endPos2.x)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.x - endPos1.x)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; + const float yi = ((startPos2.y - endPos2.y)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.y - endPos1.y)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; + + if (xi < fminf(startPos1.x, endPos1.x) || xi > fmaxf(startPos1.x, endPos1.x)) return false; + if (xi < fminf(startPos2.x, endPos2.x) || xi > fmaxf(startPos2.x, endPos2.x)) return false; + if (yi < fminf(startPos1.y, endPos1.y) || yi > fmaxf(startPos1.y, endPos1.y)) return false; + if (yi < fminf(startPos2.y, endPos2.y) || yi > fmaxf(startPos2.y, endPos2.y)) return false; + + if (collisionPoint != 0) + { + collisionPoint->x = xi; + collisionPoint->y = yi; + } + + return true; +} + +// Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold) +{ + bool collision = false; + float dxc = point.x - p1.x; + float dyc = point.y - p1.y; + float dxl = p2.x - p1.x; + float dyl = p2.y - p1.y; + float cross = dxc*dyl - dyc*dxl; + + if (fabsf(cross) < (threshold*fmaxf(fabsf(dxl), fabsf(dyl)))) + { + if (fabsf(dxl) >= fabsf(dyl)) collision = (dxl > 0)? ((p1.x <= point.x) && (point.x <= p2.x)) : ((p2.x <= point.x) && (point.x <= p1.x)); + else collision = (dyl > 0)? ((p1.y <= point.y) && (point.y <= p2.y)) : ((p2.y <= point.y) && (point.y <= p1.y)); + } + + return collision; +} + +// Get collision rectangle for two rectangles collision +Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) +{ + Rectangle rec = { 0, 0, 0, 0 }; + + if (CheckCollisionRecs(rec1, rec2)) + { + float dxx = fabsf(rec1.x - rec2.x); + float dyy = fabsf(rec1.y - rec2.y); + + if (rec1.x <= rec2.x) + { + if (rec1.y <= rec2.y) + { + rec.x = rec2.x; + rec.y = rec2.y; + rec.width = rec1.width - dxx; + rec.height = rec1.height - dyy; + } + else + { + rec.x = rec2.x; + rec.y = rec1.y; + rec.width = rec1.width - dxx; + rec.height = rec2.height - dyy; + } + } + else + { + if (rec1.y <= rec2.y) + { + rec.x = rec1.x; + rec.y = rec2.y; + rec.width = rec2.width - dxx; + rec.height = rec1.height - dyy; + } + else + { + rec.x = rec1.x; + rec.y = rec1.y; + rec.width = rec2.width - dxx; + rec.height = rec2.height - dyy; + } + } + + if (rec1.width > rec2.width) + { + if (rec.width >= rec2.width) rec.width = rec2.width; + } + else + { + if (rec.width >= rec1.width) rec.width = rec1.width; + } + + if (rec1.height > rec2.height) + { + if (rec.height >= rec2.height) rec.height = rec2.height; + } + else + { + if (rec.height >= rec1.height) rec.height = rec1.height; + } + } + + return rec; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Cubic easing in-out +// NOTE: Required for DrawLineBezier() +static float EaseCubicInOut(float t, float b, float c, float d) +{ + if ((t /= 0.5f*d) < 1) return 0.5f*c*t*t*t + b; + + t -= 2; + + return 0.5f*c*(t*t*t + 2.0f) + b; +} diff --git a/raylib/shell.html b/raylib/shell.html new file mode 100644 index 0000000..507b35a --- /dev/null +++ b/raylib/shell.html @@ -0,0 +1,327 @@ + + + + + + + raylib HTML5 GAME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + {{{ SCRIPT }}} + + diff --git a/raylib/text.c b/raylib/text.c new file mode 100644 index 0000000..3a49963 --- /dev/null +++ b/raylib/text.c @@ -0,0 +1,1878 @@ +/********************************************************************************************** +* +* raylib.text - Basic functions to load Fonts and draw Text +* +* CONFIGURATION: +* +* #define SUPPORT_FILEFORMAT_FNT +* #define SUPPORT_FILEFORMAT_TTF +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define SUPPORT_DEFAULT_FONT +* Load default raylib font on initialization to be used by DrawText() and MeasureText(). +* If no default font loaded, DrawTextEx() and MeasureTextEx() are required. +* +* #define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH +* TextSplit() function static buffer max size +* +* #define MAX_TEXTSPLIT_COUNT +* TextSplit() function static substrings pointers array (pointing to static buffer) +* +* +* DEPENDENCIES: +* stb_truetype - Load TTF file and rasterize characters data +* stb_rect_pack - Rectangles packing algorythms, required for font atlas generation +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include // Required for: malloc(), free() +#include // Required for: vsprintf() +#include // Required for: strcmp(), strstr(), strcpy(), strncpy() [Used in TextReplace()], sscanf() [Used in LoadBMFont()] +#include // Required for: va_list, va_start(), vsprintf(), va_end() [Used in TextFormat()] +#include // Requried for: toupper(), tolower() [Used in TextToUpper(), TextToLower()] + +#include "utils.h" // Required for: LoadFileText() + +#if defined(SUPPORT_FILEFORMAT_TTF) + #define STB_RECT_PACK_IMPLEMENTATION + #include "external/stb_rect_pack.h" // Required for: ttf font rectangles packaging + + #define STBTT_STATIC + #define STB_TRUETYPE_IMPLEMENTATION + #include "external/stb_truetype.h" // Required for: ttf font data reading +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_TEXT_BUFFER_LENGTH + #define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions: + // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal(), TextSplit() +#endif +#ifndef MAX_TEXT_UNICODE_CHARS + #define MAX_TEXT_UNICODE_CHARS 512 // Maximum number of unicode codepoints: GetCodepoints() +#endif +#ifndef MAX_TEXTSPLIT_COUNT + #define MAX_TEXTSPLIT_COUNT 128 // Maximum number of substrings to split: TextSplit() +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global variables +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_DEFAULT_FONT) +// Default font provided by raylib +// NOTE: Default font is loaded on InitWindow() and disposed on CloseWindow() [module: core] +static Font defaultFont = { 0 }; +#endif + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by text) +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_FNT) +static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file) +#endif + +#if defined(SUPPORT_DEFAULT_FONT) +extern void LoadFontDefault(void); +extern void UnloadFontDefault(void); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_DEFAULT_FONT) + +// Load raylib default font +extern void LoadFontDefault(void) +{ + #define BIT_CHECK(a,b) ((a) & (1u << (b))) + + // NOTE: Using UTF8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement + // Ref: http://www.utf8-chartable.de/unicode-utf8-table.pl + + defaultFont.charsCount = 224; // Number of chars included in our default font + defaultFont.charsPadding = 0; // Characters padding + + // Default font is directly defined here (data generated from a sprite font image) + // This way, we reconstruct Font without creating large global variables + // This data is automatically allocated to Stack and automatically deallocated at the end of this function + unsigned int defaultFontData[512] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f, + 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de, + 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f, + 0x00200000, 0x8001b000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xc0000fbe, 0xfbf7e00f, 0x5fbf7e7d, 0x0050bee8, 0x440808a2, 0x0a142fe8, 0x50810285, 0x0050a048, + 0x49e428a2, 0x0a142828, 0x40810284, 0x0048a048, 0x10020fbe, 0x09f7ebaf, 0xd89f3e84, 0x0047a04f, 0x09e48822, 0x0a142aa1, 0x50810284, 0x0048a048, + 0x04082822, 0x0a142fa0, 0x50810285, 0x0050a248, 0x00008fbe, 0xfbf42021, 0x5f817e7d, 0x07d09ce8, 0x00008000, 0x00000fe0, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000c0180, + 0xdfbf4282, 0x0bfbf7ef, 0x42850505, 0x004804bf, 0x50a142c6, 0x08401428, 0x42852505, 0x00a808a0, 0x50a146aa, 0x08401428, 0x42852505, 0x00081090, + 0x5fa14a92, 0x0843f7e8, 0x7e792505, 0x00082088, 0x40a15282, 0x08420128, 0x40852489, 0x00084084, 0x40a16282, 0x0842022a, 0x40852451, 0x00088082, + 0xc0bf4282, 0xf843f42f, 0x7e85fc21, 0x3e0900bf, 0x00000000, 0x00000004, 0x00000000, 0x000c0180, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000402, 0x41482000, 0x00000000, 0x00000800, + 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820, + 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0, + 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000f01, 0x00000000, 0x06000000, 0x24000000, 0x00000f01, 0x00000000, 0x09108000, + 0x24fa28a2, 0x00000f01, 0x00000000, 0x013e0000, 0x2242252a, 0x00000f52, 0x00000000, 0x038a8000, 0x2422222a, 0x00000f29, 0x00000000, 0x010a8000, + 0x2412252a, 0x00000f01, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000f01, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000, + 0x0003e000, 0x00000f00, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03, + 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202, + 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002, + 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002, + 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010, + 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7, + 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a, + 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b, + 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210, + 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe, + 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2, + 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000, + 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, + 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000, + 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + + int charsHeight = 10; + int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically + + int charsWidth[224] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, + 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 5, 7, 1, 5, 3, 7, 3, 5, 4, 1, 7, 4, 3, 5, 3, 3, 2, 5, 6, 1, 2, 2, 3, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 3, 3, 3, 3, 7, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 4, 6, + 5, 5, 5, 5, 5, 5, 9, 5, 5, 5, 5, 5, 2, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5 }; + + // Re-construct image from defaultFontData and generate OpenGL texture + //---------------------------------------------------------------------- + Image imFont = { + .data = calloc(128*128, 2), // 2 bytes per pixel (gray + alpha) + .width = 128, + .height = 128, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, + .mipmaps = 1 + }; + + // Fill image.data with defaultFontData (convert from bit to pixel!) + for (int i = 0, counter = 0; i < imFont.width*imFont.height; i += 32) + { + for (int j = 31; j >= 0; j--) + { + if (BIT_CHECK(defaultFontData[counter], j)) + { + // NOTE: We are unreferencing data as short, so, + // we must consider data as little-endian order (alpha + gray) + ((unsigned short *)imFont.data)[i + j] = 0xffff; + } + else ((unsigned short *)imFont.data)[i + j] = 0x00ff; + } + + counter++; + } + + defaultFont.texture = LoadTextureFromImage(imFont); + + // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount + //------------------------------------------------------------------------------ + + // Allocate space for our characters info data + // NOTE: This memory should be freed at end! --> CloseWindow() + defaultFont.chars = (CharInfo *)RL_MALLOC(defaultFont.charsCount*sizeof(CharInfo)); + defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.charsCount*sizeof(Rectangle)); + + int currentLine = 0; + int currentPosX = charsDivisor; + int testPosX = charsDivisor; + + for (int i = 0; i < defaultFont.charsCount; i++) + { + defaultFont.chars[i].value = 32 + i; // First char is 32 + + defaultFont.recs[i].x = (float)currentPosX; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + defaultFont.recs[i].width = (float)charsWidth[i]; + defaultFont.recs[i].height = (float)charsHeight; + + testPosX += (int)(defaultFont.recs[i].width + (float)charsDivisor); + + if (testPosX >= defaultFont.texture.width) + { + currentLine++; + currentPosX = 2*charsDivisor + charsWidth[i]; + testPosX = currentPosX; + + defaultFont.recs[i].x = (float)charsDivisor; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + } + else currentPosX = testPosX; + + // NOTE: On default font character offsets and xAdvance are not required + defaultFont.chars[i].offsetX = 0; + defaultFont.chars[i].offsetY = 0; + defaultFont.chars[i].advanceX = 0; + + // Fill character image data from fontClear data + defaultFont.chars[i].image = ImageFromImage(imFont, defaultFont.recs[i]); + } + + UnloadImage(imFont); + + defaultFont.baseSize = (int)defaultFont.recs[0].height; + + TRACELOG(LOG_INFO, "FONT: Default font loaded successfully"); +} + +// Unload raylib default font +extern void UnloadFontDefault(void) +{ + for (int i = 0; i < defaultFont.charsCount; i++) UnloadImage(defaultFont.chars[i].image); + UnloadTexture(defaultFont.texture); + RL_FREE(defaultFont.chars); + RL_FREE(defaultFont.recs); +} +#endif // SUPPORT_DEFAULT_FONT + +// Get the default font, useful to be used with extended parameters +Font GetFontDefault() +{ +#if defined(SUPPORT_DEFAULT_FONT) + return defaultFont; +#else + Font font = { 0 }; + return font; +#endif +} + +// Load Font from file into GPU memory (VRAM) +Font LoadFont(const char *fileName) +{ + // Default values for ttf font generation +#ifndef FONT_TTF_DEFAULT_SIZE + #define FONT_TTF_DEFAULT_SIZE 32 // TTF font generation default char size (char-height) +#endif +#ifndef FONT_TTF_DEFAULT_NUMCHARS + #define FONT_TTF_DEFAULT_NUMCHARS 95 // TTF font generation default charset: 95 glyphs (ASCII 32..126) +#endif +#ifndef FONT_TTF_DEFAULT_FIRST_CHAR + #define FONT_TTF_DEFAULT_FIRST_CHAR 32 // TTF font generation default first char for image sprite font (32-Space) +#endif +#ifndef FONT_TTF_DEFAULT_CHARS_PADDING + #define FONT_TTF_DEFAULT_CHARS_PADDING 4 // TTF font generation default chars padding +#endif + + Font font = { 0 }; + +#if defined(SUPPORT_FILEFORMAT_TTF) + if (IsFileExtension(fileName, ".ttf;.otf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS); + else +#endif +#if defined(SUPPORT_FILEFORMAT_FNT) + if (IsFileExtension(fileName, ".fnt")) font = LoadBMFont(fileName); + else +#endif + { + Image image = LoadImage(fileName); + if (image.data != NULL) font = LoadFontFromImage(image, MAGENTA, FONT_TTF_DEFAULT_FIRST_CHAR); + UnloadImage(image); + } + + if (font.texture.id == 0) + { + TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); + font = GetFontDefault(); + } + else SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default we set point filter (best performance) + + return font; +} + +// Load Font from TTF font file with generation parameters +// NOTE: You can pass an array with desired characters, those characters should be available in the font +// if array is NULL, default char set is selected 32..126 +Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount) +{ + Font font = { 0 }; + + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading font from memory data + font = LoadFontFromMemory(GetFileExtension(fileName), fileData, fileSize, fontSize, fontChars, charsCount); + + RL_FREE(fileData); + } + else font = GetFontDefault(); + + return font; +} + +// Load an Image font file (XNA style) +Font LoadFontFromImage(Image image, Color key, int firstChar) +{ +#ifndef MAX_GLYPHS_FROM_IMAGE + #define MAX_GLYPHS_FROM_IMAGE 256 // Maximum number of glyphs supported on image scan +#endif + + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + int charSpacing = 0; + int lineSpacing = 0; + + int x = 0; + int y = 0; + + // We allocate a temporal arrays for chars data measures, + // once we get the actual number of chars, we copy data to a sized arrays + int tempCharValues[MAX_GLYPHS_FROM_IMAGE]; + Rectangle tempCharRecs[MAX_GLYPHS_FROM_IMAGE]; + + Color *pixels = LoadImageColors(image); + + // Parse image data to get charSpacing and lineSpacing + for (y = 0; y < image.height; y++) + { + for (x = 0; x < image.width; x++) + { + if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; + } + + if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; + } + + charSpacing = x; + lineSpacing = y; + + int charHeight = 0; + int j = 0; + + while (!COLOR_EQUAL(pixels[(lineSpacing + j)*image.width + charSpacing], key)) j++; + + charHeight = j; + + // Check array values to get characters: value, x, y, w, h + int index = 0; + int lineToRead = 0; + int xPosToRead = charSpacing; + + // Parse image data to get rectangle sizes + while ((lineSpacing + lineToRead*(charHeight + lineSpacing)) < image.height) + { + while ((xPosToRead < image.width) && + !COLOR_EQUAL((pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead]), key)) + { + tempCharValues[index] = firstChar + index; + + tempCharRecs[index].x = (float)xPosToRead; + tempCharRecs[index].y = (float)(lineSpacing + lineToRead*(charHeight + lineSpacing)); + tempCharRecs[index].height = (float)charHeight; + + int charWidth = 0; + + while (!COLOR_EQUAL(pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead + charWidth], key)) charWidth++; + + tempCharRecs[index].width = (float)charWidth; + + index++; + + xPosToRead += (charWidth + charSpacing); + } + + lineToRead++; + xPosToRead = charSpacing; + } + + // NOTE: We need to remove key color borders from image to avoid weird + // artifacts on texture scaling when using TEXTURE_FILTER_BILINEAR or TEXTURE_FILTER_TRILINEAR + for (int i = 0; i < image.height*image.width; i++) if (COLOR_EQUAL(pixels[i], key)) pixels[i] = BLANK; + + // Create a new image with the processed color data (key color replaced by BLANK) + Image fontClear = { + .data = pixels, + .width = image.width, + .height = image.height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + // Create spritefont with all data parsed from image + Font font = { 0 }; + + font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture + font.charsCount = index; + font.charsPadding = 0; + + // We got tempCharValues and tempCharsRecs populated with chars data + // Now we move temp data to sized charValues and charRecs arrays + font.chars = (CharInfo *)RL_MALLOC(font.charsCount*sizeof(CharInfo)); + font.recs = (Rectangle *)RL_MALLOC(font.charsCount*sizeof(Rectangle)); + + for (int i = 0; i < font.charsCount; i++) + { + font.chars[i].value = tempCharValues[i]; + + // Get character rectangle in the font atlas texture + font.recs[i] = tempCharRecs[i]; + + // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0) + font.chars[i].offsetX = 0; + font.chars[i].offsetY = 0; + font.chars[i].advanceX = 0; + + // Fill character image data from fontClear data + font.chars[i].image = ImageFromImage(fontClear, tempCharRecs[i]); + } + + UnloadImage(fontClear); // Unload processed image once converted to texture + + font.baseSize = (int)font.recs[0].height; + + return font; +} + +// Load font from memory buffer, fileType refers to extension: i.e. ".ttf" +Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount) +{ + Font font = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + +#if defined(SUPPORT_FILEFORMAT_TTF) + if (TextIsEqual(fileExtLower, ".ttf") || + TextIsEqual(fileExtLower, ".otf")) + { + font.baseSize = fontSize; + font.charsCount = (charsCount > 0)? charsCount : 95; + font.charsPadding = 0; + font.chars = LoadFontData(fileData, dataSize, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT); + + if (font.chars != NULL) + { + font.charsPadding = FONT_TTF_DEFAULT_CHARS_PADDING; + + Image atlas = GenImageFontAtlas(font.chars, &font.recs, font.charsCount, font.baseSize, font.charsPadding, 0); + font.texture = LoadTextureFromImage(atlas); + + // Update chars[i].image to use alpha, required to be used on ImageDrawText() + for (int i = 0; i < font.charsCount; i++) + { + UnloadImage(font.chars[i].image); + font.chars[i].image = ImageFromImage(atlas, font.recs[i]); + } + + UnloadImage(atlas); + } + else font = GetFontDefault(); + } +#else + font = GetFontDefault(); +#endif + + return font; +} + +// Load font data for further use +// NOTE: Requires TTF font memory data and can generate SDF data +CharInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount, int type) +{ + // NOTE: Using some SDF generation default values, + // trades off precision with ability to handle *smaller* sizes +#ifndef FONT_SDF_CHAR_PADDING + #define FONT_SDF_CHAR_PADDING 4 // SDF font generation char padding +#endif +#ifndef FONT_SDF_ON_EDGE_VALUE + #define FONT_SDF_ON_EDGE_VALUE 128 // SDF font generation on edge value +#endif +#ifndef FONT_SDF_PIXEL_DIST_SCALE + #define FONT_SDF_PIXEL_DIST_SCALE 64.0f // SDF font generation pixel distance scale +#endif +#ifndef FONT_BITMAP_ALPHA_THRESHOLD + #define FONT_BITMAP_ALPHA_THRESHOLD 80 // Bitmap (B&W) font generation alpha threshold +#endif + + CharInfo *chars = NULL; + +#if defined(SUPPORT_FILEFORMAT_TTF) + // Load font data (including pixel data) from TTF memory file + // NOTE: Loaded information should be enough to generate font image atlas, using any packaging method + if (fileData != NULL) + { + int genFontChars = false; + stbtt_fontinfo fontInfo = { 0 }; + + if (stbtt_InitFont(&fontInfo, (unsigned char *)fileData, 0)) // Init font for data reading + { + // Calculate font scale factor + float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); + + // Calculate font basic metrics + // NOTE: ascent is equivalent to font baseline + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); + + // In case no chars count provided, default to 95 + charsCount = (charsCount > 0)? charsCount : 95; + + // Fill fontChars in case not provided externally + // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space) + + if (fontChars == NULL) + { + fontChars = (int *)RL_MALLOC(charsCount*sizeof(int)); + for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32; + genFontChars = true; + } + + chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + int chw = 0, chh = 0; // Character width and height (on generation) + int ch = fontChars[i]; // Character value to get info for + chars[i].value = ch; + + // Render a unicode codepoint to a bitmap + // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap + // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be + // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide + + if (type != FONT_SDF) chars[i].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else if (ch != 32) chars[i].image.data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, FONT_SDF_CHAR_PADDING, FONT_SDF_ON_EDGE_VALUE, FONT_SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else chars[i].image.data = NULL; + + stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); + chars[i].advanceX = (int)((float)chars[i].advanceX*scaleFactor); + + // Load characters images + chars[i].image.width = chw; + chars[i].image.height = chh; + chars[i].image.mipmaps = 1; + chars[i].image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + + chars[i].offsetY += (int)((float)ascent*scaleFactor); + + // NOTE: We create an empty image for space character, it could be further required for atlas packing + if (ch == 32) + { + Image imSpace = { + .data = calloc(chars[i].advanceX*fontSize, 2), + .width = chars[i].advanceX, + .height = fontSize, + .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE, + .mipmaps = 1 + }; + + chars[i].image = imSpace; + } + + if (type == FONT_BITMAP) + { + // Aliased bitmap (black & white) font generation, avoiding anti-aliasing + // NOTE: For optimum results, bitmap font should be generated at base pixel size + for (int p = 0; p < chw*chh; p++) + { + if (((unsigned char *)chars[i].image.data)[p] < FONT_BITMAP_ALPHA_THRESHOLD) ((unsigned char *)chars[i].image.data)[p] = 0; + else ((unsigned char *)chars[i].image.data)[p] = 255; + } + } + + // Get bounding box for character (may be offset to account for chars that dip above or below the line) + /* + int chX1, chY1, chX2, chY2; + stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2); + + TRACELOGD("FONT: Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1); + TRACELOGD("FONT: Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1); + */ + } + } + else TRACELOG(LOG_WARNING, "FONT: Failed to process TTF font data"); + + if (genFontChars) RL_FREE(fontChars); + } +#endif + + return chars; +} + +// Generate image font atlas using chars info +// NOTE: Packing method: 0-Default, 1-Skyline +#if defined(SUPPORT_FILEFORMAT_TTF) +Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCount, int fontSize, int padding, int packMethod) +{ + Image atlas = { 0 }; + + if (chars == NULL) + { + TraceLog(LOG_WARNING, "FONT: Provided chars info not valid, returning empty image atlas"); + return atlas; + } + + *charRecs = NULL; + + // In case no chars count provided we suppose default of 95 + charsCount = (charsCount > 0)? charsCount : 95; + + // NOTE: Rectangles memory is loaded here! + Rectangle *recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); + + // Calculate image size based on required pixel area + // NOTE 1: Image is forced to be squared and POT... very conservative! + // NOTE 2: SDF font characters already contain an internal padding, + // so image size would result bigger than default font type + float requiredArea = 0; + for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].image.width + 2*padding)*(chars[i].image.height + 2*padding)); + float guessSize = sqrtf(requiredArea)*1.3f; + int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT + + atlas.width = imageSize; // Atlas bitmap width + atlas.height = imageSize; // Atlas bitmap height + atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + atlas.mipmaps = 1; + + // DEBUG: We can see padding in the generated image setting a gray background... + //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; + + if (packMethod == 0) // Use basic packing algorythm + { + int offsetX = padding; + int offsetY = padding; + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < chars[i].image.height; y++) + { + for (int x = 0; x < chars[i].image.width; x++) + { + ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x]; + } + } + + // Fill chars rectangles in atlas info + recs[i].x = (float)offsetX; + recs[i].y = (float)offsetY; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; + + // Move atlas position X for next character drawing + offsetX += (chars[i].image.width + 2*padding); + + if (offsetX >= (atlas.width - chars[i].image.width - 2*padding)) + { + offsetX = padding; + + // NOTE: Be careful on offsetY for SDF fonts, by default SDF + // use an internal padding of 4 pixels, it means char rectangle + // height is bigger than fontSize, it could be up to (fontSize + 8) + offsetY += (fontSize + 2*padding); + + if (offsetY > (atlas.height - fontSize - padding)) break; + } + } + } + else if (packMethod == 1) // Use Skyline rect packing algorythm (stb_pack_rect) + { + stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context)); + stbrp_node *nodes = (stbrp_node *)RL_MALLOC(charsCount*sizeof(*nodes)); + + stbrp_init_target(context, atlas.width, atlas.height, nodes, charsCount); + stbrp_rect *rects = (stbrp_rect *)RL_MALLOC(charsCount*sizeof(stbrp_rect)); + + // Fill rectangles for packaging + for (int i = 0; i < charsCount; i++) + { + rects[i].id = i; + rects[i].w = chars[i].image.width + 2*padding; + rects[i].h = chars[i].image.height + 2*padding; + } + + // Package rectangles into atlas + stbrp_pack_rects(context, rects, charsCount); + + for (int i = 0; i < charsCount; i++) + { + // It return char rectangles in atlas + recs[i].x = rects[i].x + (float)padding; + recs[i].y = rects[i].y + (float)padding; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; + + if (rects[i].was_packed) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < chars[i].image.height; y++) + { + for (int x = 0; x < chars[i].image.width; x++) + { + ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x]; + } + } + } + else TRACELOG(LOG_WARNING, "FONT: Failed to package character (%i)", i); + } + + RL_FREE(rects); + RL_FREE(nodes); + RL_FREE(context); + } + + // TODO: Crop image if required for smaller size + + // Convert image data from GRAYSCALE to GRAY_ALPHA + unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(atlas.width*atlas.height*sizeof(unsigned char)*2); // Two channels + + for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2) + { + dataGrayAlpha[k] = 255; + dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i]; + } + + RL_FREE(atlas.data); + atlas.data = dataGrayAlpha; + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + + *charRecs = recs; + + return atlas; +} +#endif + +// Unload font chars info data (RAM) +void UnloadFontData(CharInfo *chars, int charsCount) +{ + for (int i = 0; i < charsCount; i++) UnloadImage(chars[i].image); + + RL_FREE(chars); +} + +// Unload Font from GPU memory (VRAM) +void UnloadFont(Font font) +{ + // NOTE: Make sure font is not default font (fallback) + if (font.texture.id != GetFontDefault().texture.id) + { + UnloadFontData(font.chars, font.charsCount); + UnloadTexture(font.texture); + RL_FREE(font.recs); + + TRACELOGD("FONT: Unloaded font data from RAM and VRAM"); + } +} + +// Draw current FPS +// NOTE: Uses default font +void DrawFPS(int posX, int posY) +{ + Color color = LIME; // good fps + int fps = GetFPS(); + + if (fps < 30 && fps >= 15) color = ORANGE; // warning FPS + else if (fps < 15) color = RED; // bad FPS + + DrawText(TextFormat("%2i FPS", GetFPS()), posX, posY, 20, color); +} + +// Draw text (using default font) +// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used +// NOTE: chars spacing is proportional to fontSize +void DrawText(const char *text, int posX, int posY, int fontSize, Color color) +{ + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + Vector2 position = { (float)posX, (float)posY }; + + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + DrawTextEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, color); + } +} + +// Draw text using Font +// NOTE: chars spacing is NOT proportional to fontSize +void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) +{ + int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop + + int textOffsetY = 0; // Offset between lines (on line break '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + for (int i = 0; i < length;) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + + if (codepoint == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0.0f; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + DrawTextCodepoint(font, codepoint, (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint); + } + + if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); + else textOffsetX += ((float)font.chars[index].advanceX*scaleFactor + spacing); + } + + i += codepointByteCount; // Move text bytes counter to next codepoint + } +} + +// Draw text using font inside rectangle limits +void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) +{ + DrawTextRecEx(font, text, rec, fontSize, spacing, wordWrap, tint, 0, 0, WHITE, WHITE); +} + +// Draw text using font inside rectangle limits with support for text selection +void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint) +{ + int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop + + int textOffsetY = 0; // Offset between lines (on line break '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + // Word/character wrapping mechanism variables + enum { MEASURE_STATE = 0, DRAW_STATE = 1 }; + int state = wordWrap? MEASURE_STATE : DRAW_STATE; + + int startLine = -1; // Index where to begin drawing (where a line begins) + int endLine = -1; // Index where to stop drawing (where a line ends) + int lastk = -1; // Holds last value of the character position + + for (int i = 0, k = 0; i < length; i++, k++) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + i += (codepointByteCount - 1); + + int glyphWidth = 0; + if (codepoint != '\n') + { + glyphWidth = (font.chars[index].advanceX == 0)? + (int)(font.recs[index].width*scaleFactor + spacing): + (int)(font.chars[index].advanceX*scaleFactor + spacing); + } + + // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container + // We store this info in startLine and endLine, then we change states, draw the text between those two variables + // and change states again and again recursively until the end of the text (or until we get outside of the container). + // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately + // and begin drawing on the next line before we can get outside the container. + if (state == MEASURE_STATE) + { + // TODO: There are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more + // Ref: http://jkorpela.fi/chars/spaces.html + if ((codepoint == ' ') || (codepoint == '\t') || (codepoint == '\n')) endLine = i; + + if ((textOffsetX + glyphWidth + 1) >= rec.width) + { + endLine = (endLine < 1)? i : endLine; + if (i == endLine) endLine -= codepointByteCount; + if ((startLine + codepointByteCount) == endLine) endLine = (i - codepointByteCount); + + state = !state; + } + else if ((i + 1) == length) + { + endLine = i; + + state = !state; + } + else if (codepoint == '\n') state = !state; + + if (state == DRAW_STATE) + { + textOffsetX = 0; + i = startLine; + glyphWidth = 0; + + // Save character position when we switch states + int tmp = lastk; + lastk = k - 1; + k = tmp; + } + } + else + { + if (codepoint == '\n') + { + if (!wordWrap) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + } + else + { + if (!wordWrap && ((textOffsetX + glyphWidth + 1) >= rec.width)) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + + // When text overflows rectangle height limit, just stop drawing + if ((textOffsetY + (int)(font.baseSize*scaleFactor)) > rec.height) break; + + // Draw selection background + bool isGlyphSelected = false; + if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength))) + { + DrawRectangleRec((Rectangle){ rec.x + textOffsetX - 1, rec.y + textOffsetY, (float)glyphWidth, (float)font.baseSize*scaleFactor }, selectBackTint); + isGlyphSelected = true; + } + + // Draw current character glyph + if ((codepoint != ' ') && (codepoint != '\t')) + { + DrawTextCodepoint(font, codepoint, (Vector2){ rec.x + textOffsetX, rec.y + textOffsetY }, fontSize, isGlyphSelected? selectTint : tint); + } + } + + if (wordWrap && (i == endLine)) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + startLine = endLine; + endLine = -1; + glyphWidth = 0; + selectStart += lastk - k; + k = lastk; + + state = !state; + } + } + + textOffsetX += glyphWidth; + } +} + +// Draw one character (codepoint) +void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint) +{ + // Character index position in sprite font + // NOTE: In case a codepoint is not available in the font, index returned points to '?' + int index = GetGlyphIndex(font, codepoint); + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + // Character destination rectangle on screen + // NOTE: We consider charsPadding on drawing + Rectangle dstRec = { position.x + font.chars[index].offsetX*scaleFactor - (float)font.charsPadding*scaleFactor, + position.y + font.chars[index].offsetY*scaleFactor - (float)font.charsPadding*scaleFactor, + (font.recs[index].width + 2.0f*font.charsPadding)*scaleFactor, + (font.recs[index].height + 2.0f*font.charsPadding)*scaleFactor }; + + // Character source rectangle from font texture atlas + // NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects + Rectangle srcRec = { font.recs[index].x - (float)font.charsPadding, font.recs[index].y - (float)font.charsPadding, + font.recs[index].width + 2.0f*font.charsPadding, font.recs[index].height + 2.0f*font.charsPadding }; + + // Draw the character texture on the screen + DrawTexturePro(font.texture, srcRec, dstRec, (Vector2){ 0, 0 }, 0.0f, tint); +} + +// Measure string width for default font +int MeasureText(const char *text, int fontSize) +{ + Vector2 vec = { 0.0f, 0.0f }; + + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing); + } + + return (int)vec.x; +} + +// Measure string size for Font +Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing) +{ + int len = TextLength(text); + int tempLen = 0; // Used to count longer text line num chars + int lenCounter = 0; + + float textWidth = 0.0f; + float tempTextWidth = 0.0f; // Used to count longer text line width + + float textHeight = (float)font.baseSize; + float scaleFactor = fontSize/(float)font.baseSize; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + for (int i = 0; i < len; i++) + { + lenCounter++; + + int next = 0; + letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) next = 1; + i += next - 1; + + if (letter != '\n') + { + if (font.chars[index].advanceX != 0) textWidth += font.chars[index].advanceX; + else textWidth += (font.recs[index].width + font.chars[index].offsetX); + } + else + { + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + lenCounter = 0; + textWidth = 0; + textHeight += ((float)font.baseSize*1.5f); // NOTE: Fixed line spacing of 1.5 lines + } + + if (tempLen < lenCounter) tempLen = lenCounter; + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + Vector2 vec = { 0 }; + vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure + vec.y = textHeight*scaleFactor; + + return vec; +} + +// Returns index position for a unicode character on spritefont +int GetGlyphIndex(Font font, int codepoint) +{ +#ifndef GLYPH_NOTFOUND_CHAR_FALLBACK + #define GLYPH_NOTFOUND_CHAR_FALLBACK 63 // Character used if requested codepoint is not found: '?' +#endif + +// Support charsets with any characters order +#define SUPPORT_UNORDERED_CHARSET +#if defined(SUPPORT_UNORDERED_CHARSET) + int index = GLYPH_NOTFOUND_CHAR_FALLBACK; + + for (int i = 0; i < font.charsCount; i++) + { + if (font.chars[i].value == codepoint) + { + index = i; + break; + } + } + + return index; +#else + return (codepoint - 32); +#endif +} + +//---------------------------------------------------------------------------------- +// Text strings management functions +//---------------------------------------------------------------------------------- +// Get text length in bytes, check for \0 character +unsigned int TextLength(const char *text) +{ + unsigned int length = 0; //strlen(text) + + if (text != NULL) + { + while (*text++) length++; + } + + return length; +} + +// Formatting of text with variables to 'embed' +// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times +const char *TextFormat(const char *text, ...) +{ +#ifndef MAX_TEXTFORMAT_BUFFERS + #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting +#endif + + // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations + static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; + static int index = 0; + + char *currentBuffer = buffers[index]; + memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using + + va_list args; + va_start(args, text); + vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); + va_end(args); + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + + return currentBuffer; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +#if defined(SUPPORT_TEXT_MANIPULATION) +// Copy one string to another, returns bytes copied +int TextCopy(char *dst, const char *src) +{ + int bytes = 0; + + if (dst != NULL) + { + while (*src != '\0') + { + *dst = *src; + dst++; + src++; + + bytes++; + } + + *dst = '\0'; + } + + return bytes; +} + +// Check if two text string are equal +// REQUIRES: strcmp() +bool TextIsEqual(const char *text1, const char *text2) +{ + bool result = false; + + if (strcmp(text1, text2) == 0) result = true; + + return result; +} + +// Get a piece of a text string +const char *TextSubtext(const char *text, int position, int length) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + int textLength = TextLength(text); + + if (position >= textLength) + { + position = textLength - 1; + length = 0; + } + + if (length >= textLength) length = textLength; + + for (int c = 0 ; c < length ; c++) + { + *(buffer + c) = *(text + position); + text++; + } + + *(buffer + length) = '\0'; + + return buffer; +} + +// Replace text string +// REQUIRES: strstr(), strncpy(), strcpy() +// WARNING: Internally allocated memory must be freed by the user (if return != NULL) +char *TextReplace(char *text, const char *replace, const char *by) +{ + // Sanity checks and initialization + if (!text || !replace || !by) return NULL; + + char *result; + + char *insertPoint; // Next insert point + char *temp; // Temp pointer + int replaceLen; // Replace string length of (the string to remove) + int byLen; // Replacement length (the string to replace replace by) + int lastReplacePos; // Distance between replace and end of last replace + int count; // Number of replacements + + replaceLen = TextLength(replace); + if (replaceLen == 0) return NULL; // Empty replace causes infinite loop during count + + byLen = TextLength(by); + + // Count the number of replacements needed + insertPoint = text; + for (count = 0; (temp = strstr(insertPoint, replace)); count++) insertPoint = temp + replaceLen; + + // Allocate returning string and point temp to it + temp = result = RL_MALLOC(TextLength(text) + (byLen - replaceLen)*count + 1); + + if (!result) return NULL; // Memory could not be allocated + + // First time through the loop, all the variable are set correctly from here on, + // temp points to the end of the result string + // insertPoint points to the next occurrence of replace in text + // text points to the remainder of text after "end of replace" + while (count--) + { + insertPoint = strstr(text, replace); + lastReplacePos = (int)(insertPoint - text); + temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; + temp = strcpy(temp, by) + byLen; + text += lastReplacePos + replaceLen; // Move to next "end of replace" + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + strcpy(temp, text); + + return result; +} + +// Insert text in a specific position, moves all text forward +// WARNING: Allocated memory should be manually freed +char *TextInsert(const char *text, const char *insert, int position) +{ + int textLen = TextLength(text); + int insertLen = TextLength(insert); + + char *result = (char *)RL_MALLOC(textLen + insertLen + 1); + + for (int i = 0; i < position; i++) result[i] = text[i]; + for (int i = position; i < insertLen + position; i++) result[i] = insert[i]; + for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i]; + + result[textLen + insertLen] = '\0'; // Make sure text string is valid! + + return result; +} + +// Join text strings with delimiter +// REQUIRES: memset(), memcpy() +const char *TextJoin(const char **textList, int count, const char *delimiter) +{ + static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(text, 0, MAX_TEXT_BUFFER_LENGTH); + char *textPtr = text; + + int totalLength = 0; + int delimiterLen = TextLength(delimiter); + + for (int i = 0; i < count; i++) + { + int textLength = TextLength(textList[i]); + + // Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH + if ((totalLength + textLength) < MAX_TEXT_BUFFER_LENGTH) + { + memcpy(textPtr, textList[i], textLength); + totalLength += textLength; + textPtr += textLength; + + if ((delimiterLen > 0) && (i < (count - 1))) + { + memcpy(textPtr, delimiter, delimiterLen); + totalLength += delimiterLen; + textPtr += delimiterLen; + } + } + } + + return text; +} + +// Split string into multiple strings +// REQUIRES: memset() +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by MAX_TEXTSPLIT_COUNT + // 2. Maximum size of text to split is MAX_TEXT_BUFFER_LENGTH + + static const char *result[MAX_TEXTSPLIT_COUNT] = { NULL }; + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == MAX_TEXTSPLIT_COUNT) break; + } + } + } + + *count = counter; + return result; +} + +// Append text at specific position and move cursor! +// REQUIRES: strcpy() +void TextAppend(char *text, const char *append, int *position) +{ + strcpy(text + *position, append); + *position += TextLength(append); +} + +// Find first text occurrence within a string +// REQUIRES: strstr() +int TextFindIndex(const char *text, const char *find) +{ + int position = -1; + + char *ptr = strstr(text, find); + + if (ptr != NULL) position = (int)(ptr - text); + + return position; +} + +// Get upper case version of provided string +// REQUIRES: toupper() +const char *TextToUpper(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + if (text[i] != '\0') + { + buffer[i] = (char)toupper(text[i]); + //if ((text[i] >= 'a') && (text[i] <= 'z')) buffer[i] = text[i] - 32; + + // TODO: Support Utf8 diacritics! + //if ((text[i] >= 'à') && (text[i] <= 'ý')) buffer[i] = text[i] - 32; + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get lower case version of provided string +// REQUIRES: tolower() +const char *TextToLower(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + if (text[i] != '\0') + { + buffer[i] = (char)tolower(text[i]); + //if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32; + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get Pascal case notation version of provided string +// REQUIRES: toupper() +const char *TextToPascal(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + buffer[0] = (char)toupper(text[0]); + + for (int i = 1, j = 1; i < MAX_TEXT_BUFFER_LENGTH; i++, j++) + { + if (text[j] != '\0') + { + if (text[j] != '_') buffer[i] = text[j]; + else + { + j++; + buffer[i] = (char)toupper(text[j]); + } + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Encode text codepoint into utf8 text +// REQUIRES: memcpy() +// WARNING: Allocated memory should be manually freed +char *TextToUtf8(int *codepoints, int length) +{ + // We allocate enough memory fo fit all possible codepoints + // NOTE: 5 bytes for every codepoint should be enough + char *text = (char *)RL_CALLOC(length*5, 1); + const char *utf8 = NULL; + int size = 0; + + for (int i = 0, bytes = 0; i < length; i++) + { + utf8 = CodepointToUtf8(codepoints[i], &bytes); + memcpy(text + size, utf8, bytes); + size += bytes; + } + + // Resize memory to text length + string NULL terminator + void *ptr = RL_REALLOC(text, size + 1); + + if (ptr != NULL) text = (char *)ptr; + + return text; +} + +// Encode codepoint into utf8 text (char array length returned as parameter) +RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength) +{ + static char utf8[6] = { 0 }; + int length = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + length = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + length = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + length = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + length = 4; + } + + *byteLength = length; + + return utf8; +} + +// Get all codepoints in a string, codepoints count returned by parameters +// REQUIRES: memset() +int *GetCodepoints(const char *text, int *count) +{ + static int codepoints[MAX_TEXT_UNICODE_CHARS] = { 0 }; + memset(codepoints, 0, MAX_TEXT_UNICODE_CHARS*sizeof(int)); + + int bytesProcessed = 0; + int textLength = TextLength(text); + int codepointsCount = 0; + + for (int i = 0; i < textLength; codepointsCount++) + { + codepoints[codepointsCount] = GetNextCodepoint(text + i, &bytesProcessed); + i += bytesProcessed; + } + + *count = codepointsCount; + + return codepoints; +} + +// Returns total number of characters(codepoints) in a UTF8 encoded text, until '\0' is found +// NOTE: If an invalid UTF8 sequence is encountered a '?'(0x3f) codepoint is counted instead +int GetCodepointsCount(const char *text) +{ + unsigned int len = 0; + char *ptr = (char *)&text[0]; + + while (*ptr != '\0') + { + int next = 0; + int letter = GetNextCodepoint(ptr, &next); + + if (letter == 0x3f) ptr += 1; + else ptr += next; + + len++; + } + + return len; +} +#endif // SUPPORT_TEXT_MANIPULATION + +// Returns next codepoint in a UTF8 encoded text, scanning until '\0' is found +// When a invalid UTF8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +// TODO: Optimize this code for speed!! +int GetNextCodepoint(const char *text, int *bytesProcessed) +{ +/* + UTF8 specs from https://www.ietf.org/rfc/rfc3629.txt + + Char. number range | UTF-8 octet sequence + (hexadecimal) | (binary) + --------------------+--------------------------------------------- + 0000 0000-0000 007F | 0xxxxxxx + 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx +*/ + // NOTE: on decode errors we return as soon as possible + + int code = 0x3f; // Codepoint (defaults to '?') + int octet = (unsigned char)(text[0]); // The first UTF8 octet + *bytesProcessed = 1; + + if (octet <= 0x7f) + { + // Only one octet (ASCII range x00-7F) + code = text[0]; + } + else if ((octet & 0xe0) == 0xc0) + { + // Two octets + // [0]xC2-DF [1]UTF8-tail(x80-BF) + unsigned char octet1 = text[1]; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if ((octet >= 0xc2) && (octet <= 0xdf)) + { + code = ((octet & 0x1f) << 6) | (octet1 & 0x3f); + *bytesProcessed = 2; + } + } + else if ((octet & 0xf0) == 0xe0) + { + // Three octets + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + /* + [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) + [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF) + [0]xED [1]x80-9F [2]UTF8-tail(x80-BF) + [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) + */ + + if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || + ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *bytesProcessed = 2; return code; } + + if ((octet >= 0xe0) && (0 <= 0xef)) + { + code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); + *bytesProcessed = 3; + } + } + else if ((octet & 0xf8) == 0xf0) + { + // Four octets + if (octet > 0xf4) return code; + + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + unsigned char octet3 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + octet3 = text[3]; + + if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *bytesProcessed = 4; return code; } // Unexpected sequence + + /* + [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail + [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail + [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail + */ + + if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) || + ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if (octet >= 0xf0) + { + code = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f); + *bytesProcessed = 4; + } + } + + if (code > 0x10ffff) code = 0x3f; // Codepoints after U+10ffff are invalid + + return code; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_FNT) + +// Read a line from memory +// REQUIRES: memcpy() +// NOTE: Returns the number of bytes read +static int GetLine(const char *origin, char *buffer, int maxLength) +{ + int count = 0; + for (; count < maxLength; count++) if (origin[count] == '\n') break; + memcpy(buffer, origin, count); + return count; +} + +// Load a BMFont file (AngelCode font file) +// REQUIRES: strstr(), sscanf(), strrchr(), memcpy() +static Font LoadBMFont(const char *fileName) +{ + #define MAX_BUFFER_SIZE 256 + + Font font = { 0 }; + + char buffer[MAX_BUFFER_SIZE] = { 0 }; + char *searchPoint = NULL; + + int fontSize = 0; + int charsCount = 0; + + int imWidth = 0; + int imHeight = 0; + char imFileName[129]; + + int base = 0; // Useless data + + char *fileText = LoadFileText(fileName); + + if (fileText == NULL) return font; + + char *fileTextPtr = fileText; + + // NOTE: We skip first line, it contains no useful information + int lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + fileTextPtr += (lineBytes + 1); + + // Read line data + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "lineHeight"); + sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i", &fontSize, &base, &imWidth, &imHeight); + fileTextPtr += (lineBytes + 1); + + TRACELOGD("FONT: [%s] Loaded font info:", fileName); + TRACELOGD(" > Base size: %i", fontSize); + TRACELOGD(" > Texture scale: %ix%i", imWidth, imHeight); + + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "file"); + sscanf(searchPoint, "file=\"%128[^\"]\"", imFileName); + fileTextPtr += (lineBytes + 1); + + TRACELOGD(" > Texture filename: %s", imFileName); + + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "count"); + sscanf(searchPoint, "count=%i", &charsCount); + fileTextPtr += (lineBytes + 1); + + TRACELOGD(" > Chars count: %i", charsCount); + + // Compose correct path using route of .fnt file (fileName) and imFileName + char *imPath = NULL; + char *lastSlash = NULL; + + lastSlash = strrchr(fileName, '/'); + if (lastSlash == NULL) lastSlash = strrchr(fileName, '\\'); + + if (lastSlash != NULL) + { + // NOTE: We need some extra space to avoid memory corruption on next allocations! + imPath = RL_CALLOC(TextLength(fileName) - TextLength(lastSlash) + TextLength(imFileName) + 4, 1); + memcpy(imPath, fileName, TextLength(fileName) - TextLength(lastSlash) + 1); + memcpy(imPath + TextLength(fileName) - TextLength(lastSlash) + 1, imFileName, TextLength(imFileName)); + } + else imPath = imFileName; + + TRACELOGD(" > Image loading path: %s", imPath); + + Image imFont = LoadImage(imPath); + + if (imFont.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel + Image imFontAlpha = { + .data = calloc(imFont.width*imFont.height, 2), + .width = imFont.width, + .height = imFont.height, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, + .mipmaps = 1 + }; + + for (int p = 0, i = 0; p < (imFont.width*imFont.height*2); p += 2, i++) + { + ((unsigned char *)(imFontAlpha.data))[p] = 0xff; + ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFont.data)[i]; + } + + UnloadImage(imFont); + imFont = imFontAlpha; + } + + font.texture = LoadTextureFromImage(imFont); + + if (lastSlash != NULL) RL_FREE(imPath); + + // Fill font characters info data + font.baseSize = fontSize; + font.charsCount = charsCount; + font.charsPadding = 0; + font.chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); + font.recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); + + int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX; + + for (int i = 0; i < charsCount; i++) + { + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i", + &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX); + fileTextPtr += (lineBytes + 1); + + // Get character rectangle in the font atlas texture + font.recs[i] = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight }; + + // Save data properly in sprite font + font.chars[i].value = charId; + font.chars[i].offsetX = charOffsetX; + font.chars[i].offsetY = charOffsetY; + font.chars[i].advanceX = charAdvanceX; + + // Fill character image data from imFont data + font.chars[i].image = ImageFromImage(imFont, font.recs[i]); + } + + UnloadImage(imFont); + RL_FREE(fileText); + + if (font.texture.id == 0) + { + UnloadFont(font); + font = GetFontDefault(); + TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load texture, reverted to default font", fileName); + } + else TRACELOG(LOG_INFO, "FONT: [%s] Font loaded successfully", fileName); + + return font; +} +#endif diff --git a/raylib/textures.c b/raylib/textures.c new file mode 100644 index 0000000..de0d76b --- /dev/null +++ b/raylib/textures.c @@ -0,0 +1,4588 @@ +/********************************************************************************************** +* +* raylib.textures - Basic functions to load and draw Textures (2d) +* +* CONFIGURATION: +* +* #define SUPPORT_FILEFORMAT_BMP +* #define SUPPORT_FILEFORMAT_PNG +* #define SUPPORT_FILEFORMAT_TGA +* #define SUPPORT_FILEFORMAT_JPG +* #define SUPPORT_FILEFORMAT_GIF +* #define SUPPORT_FILEFORMAT_PSD +* #define SUPPORT_FILEFORMAT_PIC +* #define SUPPORT_FILEFORMAT_HDR +* #define SUPPORT_FILEFORMAT_DDS +* #define SUPPORT_FILEFORMAT_PKM +* #define SUPPORT_FILEFORMAT_KTX +* #define SUPPORT_FILEFORMAT_PVR +* #define SUPPORT_FILEFORMAT_ASTC +* Select desired fileformats to be supported for image data loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define SUPPORT_IMAGE_EXPORT +* Support image export in multiple file formats +* +* #define SUPPORT_IMAGE_MANIPULATION +* Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... +* If not defined only three image editing functions supported: ImageFormat(), ImageAlphaMask(), ImageToPOT() +* +* #define SUPPORT_IMAGE_GENERATION +* Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) +* +* DEPENDENCIES: +* stb_image - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC) +* NOTE: stb_image has been slightly modified to support Android platform. +* stb_image_resize - Multiple image resize algorythms +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include // Required for: malloc(), free() +#include // Required for: strlen() [Used in ImageTextEx()] +#include // Required for: fabsf() + +#include "utils.h" // Required for: fopen() Android mapping + +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2 + // Required for: rlLoadTexture() rlUnloadTexture(), + // rlGenerateMipmaps(), some funcs for DrawTexturePro() + +// Support only desired texture formats on stb_image +#if !defined(SUPPORT_FILEFORMAT_BMP) + #define STBI_NO_BMP +#endif +#if !defined(SUPPORT_FILEFORMAT_PNG) + #define STBI_NO_PNG +#endif +#if !defined(SUPPORT_FILEFORMAT_TGA) + #define STBI_NO_TGA +#endif +#if !defined(SUPPORT_FILEFORMAT_JPG) + #define STBI_NO_JPEG // Image format .jpg and .jpeg +#endif +#if !defined(SUPPORT_FILEFORMAT_PSD) + #define STBI_NO_PSD +#endif +#if !defined(SUPPORT_FILEFORMAT_GIF) + #define STBI_NO_GIF +#endif +#if !defined(SUPPORT_FILEFORMAT_PIC) + #define STBI_NO_PIC +#endif +#if !defined(SUPPORT_FILEFORMAT_HDR) + #define STBI_NO_HDR +#endif + +// Image fileformats not supported by default +#define STBI_NO_PIC +#define STBI_NO_PNM // Image format .ppm and .pgm + +#if (defined(SUPPORT_FILEFORMAT_BMP) || \ + defined(SUPPORT_FILEFORMAT_PNG) || \ + defined(SUPPORT_FILEFORMAT_TGA) || \ + defined(SUPPORT_FILEFORMAT_JPG) || \ + defined(SUPPORT_FILEFORMAT_PSD) || \ + defined(SUPPORT_FILEFORMAT_GIF) || \ + defined(SUPPORT_FILEFORMAT_PIC) || \ + defined(SUPPORT_FILEFORMAT_HDR)) + + #define STBI_MALLOC RL_MALLOC + #define STBI_FREE RL_FREE + #define STBI_REALLOC RL_REALLOC + + #define STB_IMAGE_IMPLEMENTATION + #include "external/stb_image.h" // Required for: stbi_load_from_file() + // NOTE: Used to read image data (multiple formats support) +#endif + +#if (defined(SUPPORT_IMAGE_EXPORT) || defined(SUPPORT_COMPRESSION_API)) + #define STBIW_MALLOC RL_MALLOC + #define STBIW_FREE RL_FREE + #define STBIW_REALLOC RL_REALLOC + + #define STB_IMAGE_WRITE_IMPLEMENTATION + #include "external/stb_image_write.h" // Required for: stbi_write_*() +#endif + +#if defined(SUPPORT_IMAGE_MANIPULATION) + #define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size)) + #define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr)) + + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include "external/stb_image_resize.h" // Required for: stbir_resize_uint8() [ImageResize()] +#endif + +#if defined(SUPPORT_IMAGE_GENERATION) + #define STB_PERLIN_IMPLEMENTATION + #include "external/stb_perlin.h" // Required for: stb_perlin_fbm_noise3 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD + #define PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// It's lonely here... + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by text) +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_DDS) +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize); // Load DDS file data +#endif +#if defined(SUPPORT_FILEFORMAT_PKM) +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize); // Load PKM file data +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize); // Load KTX file data +static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file +#endif +#if defined(SUPPORT_FILEFORMAT_PVR) +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize); // Load PVR file data +#endif +#if defined(SUPPORT_FILEFORMAT_ASTC) +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize); // Load ASTC file data +#endif +static Vector4 *LoadImageDataNormalized(Image image); // Load pixel data from image as Vector4 array (float normalized) + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Load image from file into CPU memory (RAM) +Image LoadImage(const char *fileName) +{ + Image image = { 0 }; + +#if defined(SUPPORT_FILEFORMAT_PNG) || \ + defined(SUPPORT_FILEFORMAT_BMP) || \ + defined(SUPPORT_FILEFORMAT_TGA) || \ + defined(SUPPORT_FILEFORMAT_JPG) || \ + defined(SUPPORT_FILEFORMAT_GIF) || \ + defined(SUPPORT_FILEFORMAT_PIC) || \ + defined(SUPPORT_FILEFORMAT_HDR) || \ + defined(SUPPORT_FILEFORMAT_PSD) +#define STBI_REQUIRED +#endif + + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading image from memory data + image = LoadImageFromMemory(GetFileExtension(fileName), fileData, fileSize); + + if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: [%s] Data loaded successfully (%ix%i)", fileName, image.width, image.height); + else TRACELOG(LOG_WARNING, "IMAGE: [%s] Failed to load data", fileName); + + RL_FREE(fileData); + } + + return image; +} + +// Load an image from RAW file data +Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize) +{ + Image image = { 0 }; + + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + unsigned char *dataPtr = fileData; + unsigned int size = GetPixelDataSize(width, height, format); + + if (headerSize > 0) dataPtr += headerSize; + + image.data = RL_MALLOC(size); // Allocate required memory in bytes + memcpy(image.data, dataPtr, size); // Copy required data to image + image.width = width; + image.height = height; + image.mipmaps = 1; + image.format = format; + + RL_FREE(fileData); + } + + return image; +} + +// Load animated image data +// - Image.data buffer includes all frames: [image#0][image#1][image#2][...] +// - Number of frames is returned through 'frames' parameter +// - All frames are returned in RGBA format +// - Frames delay data is discarded +Image LoadImageAnim(const char *fileName, int *frames) +{ + Image image = { 0 }; + int framesCount = 1; + +#if defined(SUPPORT_FILEFORMAT_GIF) + if (IsFileExtension(fileName, ".gif")) + { + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + int comp = 0; + int **delays = NULL; + image.data = stbi_load_gif_from_memory(fileData, dataSize, delays, &image.width, &image.height, &framesCount, &comp, 4); + + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + RL_FREE(fileData); + RL_FREE(delays); // NOTE: Frames delays are discarded + } + } +#else + if (false) { } +#endif + else image = LoadImage(fileName); + + // TODO: Support APNG animated images? + + *frames = framesCount; + return image; +} + +// Load image from memory buffer, fileType refers to extension: i.e. ".png" +Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Image image = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + +#if defined(SUPPORT_FILEFORMAT_PNG) + if ((TextIsEqual(fileExtLower, ".png")) +#else + if ((false) +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) + || (TextIsEqual(fileExtLower, ".bmp")) +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) + || (TextIsEqual(fileExtLower, ".tga")) +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) + || (TextIsEqual(fileExtLower, ".jpg") || + TextIsEqual(fileExtLower, ".jpeg")) +#endif +#if defined(SUPPORT_FILEFORMAT_GIF) + || (TextIsEqual(fileExtLower, ".gif")) +#endif +#if defined(SUPPORT_FILEFORMAT_PIC) + || (TextIsEqual(fileExtLower, ".pic")) +#endif +#if defined(SUPPORT_FILEFORMAT_PSD) + || (TextIsEqual(fileExtLower, ".psd")) +#endif + ) + { +#if defined(STBI_REQUIRED) + // NOTE: Using stb_image to load images (Supports multiple image formats) + + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (comp == 2) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } +#endif + } +#if defined(SUPPORT_FILEFORMAT_HDR) + else if (TextIsEqual(fileExtLower, ".hdr")) + { +#if defined(STBI_REQUIRED) + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_R32; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; + else + { + TRACELOG(LOG_WARNING, "IMAGE: HDR file format not supported"); + UnloadImage(image); + } + } +#endif + } +#endif +#if defined(SUPPORT_FILEFORMAT_DDS) + else if (TextIsEqual(fileExtLower, ".dds")) image = LoadDDS(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_PKM) + else if (TextIsEqual(fileExtLower, ".pkm")) image = LoadPKM(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) + else if (TextIsEqual(fileExtLower, ".ktx")) image = LoadKTX(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_PVR) + else if (TextIsEqual(fileExtLower, ".pvr")) image = LoadPVR(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_ASTC) + else if (TextIsEqual(fileExtLower, ".astc")) image = LoadASTC(fileData, dataSize); +#endif + else TRACELOG(LOG_WARNING, "IMAGE: File format not supported"); + + return image; +} + +// Unload image from CPU memory (RAM) +void UnloadImage(Image image) +{ + RL_FREE(image.data); +} + +// Export image data to file +// NOTE: File format depends on fileName extension +bool ExportImage(Image image, const char *fileName) +{ + int success = 0; + +#if defined(SUPPORT_IMAGE_EXPORT) + int channels = 4; + bool allocatedData = false; + unsigned char *imgData = (unsigned char *)image.data; + + if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; + else + { + // NOTE: Getting Color array as RGBA unsigned char values + imgData = (unsigned char *)LoadImageColors(image); + allocatedData = true; + } + +#if defined(SUPPORT_FILEFORMAT_PNG) + if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, channels, imgData, image.width*channels); +#else + if (false) {} +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) + else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, channels, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) + else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, channels, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) + else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) + else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw pixel data (without header) + // NOTE: It's up to the user to track image parameters + success = SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); + } + + if (allocatedData) RL_FREE(imgData); +#endif // SUPPORT_IMAGE_EXPORT + + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); + + return success; +} + +// Export image as code file (.h) defining an array of bytes +bool ExportImageAsCode(Image image, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + int dataSize = GetPixelDataSize(image.width, image.height, image.format); + + // NOTE: Text data buffer size is estimated considering image data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(6*dataSize + 2000, sizeof(char)); + + int bytesCount = 0; + bytesCount += sprintf(txtData + bytesCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + bytesCount += sprintf(txtData + bytesCount, "// feedback and support: ray[at]raylib.com //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// Copyright (c) 2020 Ramon Santamaria (@raysan5) //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + + // Add image information + bytesCount += sprintf(txtData + bytesCount, "// Image data information\n"); + bytesCount += sprintf(txtData + bytesCount, "#define %s_WIDTH %i\n", varFileName, image.width); + bytesCount += sprintf(txtData + bytesCount, "#define %s_HEIGHT %i\n", varFileName, image.height); + bytesCount += sprintf(txtData + bytesCount, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); + + bytesCount += sprintf(txtData + bytesCount, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) bytesCount += sprintf(txtData + bytesCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); + bytesCount += sprintf(txtData + bytesCount, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + return success; +} + +//------------------------------------------------------------------------------------ +// Image generation functions +//------------------------------------------------------------------------------------ +// Generate image: plain color +Image GenImageColor(int width, int height, Color color) +{ + Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); + + for (int i = 0; i < width*height; i++) pixels[i] = color; + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +#if defined(SUPPORT_IMAGE_GENERATION) +// Generate image: vertical gradient +Image GenImageGradientV(int width, int height, Color top, Color bottom) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int j = 0; j < height; j++) + { + float factor = (float)j/(float)height; + for (int i = 0; i < width; i++) + { + pixels[j*width + i].r = (int)((float)bottom.r*factor + (float)top.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)bottom.g*factor + (float)top.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)bottom.b*factor + (float)top.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)bottom.a*factor + (float)top.a*(1.f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: horizontal gradient +Image GenImageGradientH(int width, int height, Color left, Color right) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int i = 0; i < width; i++) + { + float factor = (float)i/(float)width; + for (int j = 0; j < height; j++) + { + pixels[j*width + i].r = (int)((float)right.r*factor + (float)left.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)right.g*factor + (float)left.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)right.b*factor + (float)left.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)right.a*factor + (float)left.a*(1.f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: radial gradient +Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; + + float centerX = (float)width/2.0f; + float centerY = (float)height/2.0f; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float dist = hypotf((float)x - centerX, (float)y - centerY); + float factor = (dist - radius*density)/(radius*(1.0f - density)); + + factor = (float)fmax(factor, 0.0f); + factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check + + pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); + pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); + pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); + pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: checked +Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; + else pixels[y*width + x] = col2; + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: white noise +Image GenImageWhiteNoise(int width, int height, float factor) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int i = 0; i < width*height; i++) + { + if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; + else pixels[i] = BLACK; + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: perlin noise +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float nx = (float)(x + offsetX)*scale/(float)width; + float ny = (float)(y + offsetY)*scale/(float)height; + + // Typical values to start playing with: + // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) + // gain = 0.5 -- relative weighting applied to each successive octave + // octaves = 6 -- number of "octaves" of noise3() to sum + + // NOTE: We need to translate the data from [-1..1] to [0..1] + float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f; + + int intensity = (int)(p*255.0f); + pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: cellular algorithm. Bigger tileSize means bigger cells +Image GenImageCellular(int width, int height, int tileSize) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + int seedsPerRow = width/tileSize; + int seedsPerCol = height/tileSize; + int seedsCount = seedsPerRow*seedsPerCol; + + Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); + + for (int i = 0; i < seedsCount; i++) + { + int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + seeds[i] = (Vector2){ (float)x, (float)y}; + } + + for (int y = 0; y < height; y++) + { + int tileY = y/tileSize; + + for (int x = 0; x < width; x++) + { + int tileX = x/tileSize; + + float minDistance = (float)strtod("Inf", NULL); + + // Check all adjacent tiles + for (int i = -1; i < 2; i++) + { + if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; + + for (int j = -1; j < 2; j++) + { + if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; + + Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; + + float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); + minDistance = (float)fmin(minDistance, dist); + } + } + + // I made this up but it seems to give good results at all tile sizes + int intensity = (int)(minDistance*256.0f/tileSize); + if (intensity > 255) intensity = 255; + + pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + } + } + + RL_FREE(seeds); + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} +#endif // SUPPORT_IMAGE_GENERATION + +//------------------------------------------------------------------------------------ +// Image manipulation functions +//------------------------------------------------------------------------------------ +// Copy an image to a new image +Image ImageCopy(Image image) +{ + Image newImage = { 0 }; + + int width = image.width; + int height = image.height; + int size = 0; + + for (int i = 0; i < image.mipmaps; i++) + { + size += GetPixelDataSize(width, height, image.format); + + width /= 2; + height /= 2; + + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; + } + + newImage.data = RL_MALLOC(size); + + if (newImage.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newImage.data, image.data, size); + + newImage.width = image.width; + newImage.height = image.height; + newImage.mipmaps = image.mipmaps; + newImage.format = image.format; + } + + return newImage; +} + +// Create an image from another image piece +Image ImageFromImage(Image image, Rectangle rec) +{ + Image result = { 0 }; + + int bytesPerPixel = GetPixelDataSize(1, 1, image.format); + + // TODO: Check rec is valid? + + result.width = (int)rec.width; + result.height = (int)rec.height; + result.data = RL_CALLOC((int)(rec.width*rec.height)*bytesPerPixel, 1); + result.format = image.format; + result.mipmaps = 1; + + for (int y = 0; y < rec.height; y++) + { + memcpy(((unsigned char *)result.data) + y*(int)rec.width*bytesPerPixel, ((unsigned char *)image.data) + ((y + (int)rec.y)*image.width + (int)rec.x)*bytesPerPixel, (int)rec.width*bytesPerPixel); + } + + return result; +} + +// Crop an image to area defined by a rectangle +// NOTE: Security checks are performed in case rectangle goes out of bounds +void ImageCrop(Image *image, Rectangle crop) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Security checks to validate crop rectangle + if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } + if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } + if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; + if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; + if ((crop.x > image->width) || (crop.y > image->height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); + return; + } + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + + unsigned char *croppedData = (unsigned char *)RL_MALLOC((int)(crop.width*crop.height)*bytesPerPixel); + + // OPTION 1: Move cropped data line-by-line + for (int y = (int)crop.y, offsetSize = 0; y < (int)(crop.y + crop.height); y++) + { + memcpy(croppedData + offsetSize, ((unsigned char *)image->data) + (y*image->width + (int)crop.x)*bytesPerPixel, (int)crop.width*bytesPerPixel); + offsetSize += ((int)crop.width*bytesPerPixel); + } + + /* + // OPTION 2: Move cropped data pixel-by-pixel or byte-by-byte + for (int y = (int)crop.y; y < (int)(crop.y + crop.height); y++) + { + for (int x = (int)crop.x; x < (int)(crop.x + crop.width); x++) + { + //memcpy(croppedData + ((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) croppedData[((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; + } + } + */ + + RL_FREE(image->data); + image->data = croppedData; + image->width = (int)crop.width; + image->height = (int)crop.height; + } +} + +// Convert image data to desired format +void ImageFormat(Image *image, int newFormat) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if ((newFormat != 0) && (image->format != newFormat)) + { + if ((image->format < PIXELFORMAT_COMPRESSED_DXT1_RGB) && (newFormat < PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + Vector4 *pixels = LoadImageDataNormalized(*image); // Supports 8 to 32 bit per channel + + RL_FREE(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... + image->data = NULL; + image->format = newFormat; + + int k = 0; + + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((unsigned char *)image->data)[i] = (unsigned char)((pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)*255.0f); + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char)); + + for (int i = 0; i < image->width*image->height*2; i += 2, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)((pixels[k].x*0.299f + (float)pixels[k].y*0.587f + (float)pixels[k].z*0.114f)*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f); + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*31.0f)); + g = (unsigned char)(round(pixels[i].y*63.0f)); + b = (unsigned char)(round(pixels[i].z*31.0f)); + + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char)); + + for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); + ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*31.0f)); + g = (unsigned char)(round(pixels[i].y*31.0f)); + b = (unsigned char)(round(pixels[i].z*31.0f)); + a = (pixels[i].w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; + + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*15.0f)); + g = (unsigned char)(round(pixels[i].y*15.0f)); + b = (unsigned char)(round(pixels[i].z*15.0f)); + a = (unsigned char)(round(pixels[i].w*15.0f)); + + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char)); + + for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); + ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); + ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit + + image->data = (float *)RL_MALLOC(image->width*image->height*sizeof(float)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float)); + + for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) + { + ((float *)image->data)[i] = pixels[k].x; + ((float *)image->data)[i + 1] = pixels[k].y; + ((float *)image->data)[i + 2] = pixels[k].z; + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float)); + + for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) + { + ((float *)image->data)[i] = pixels[k].x; + ((float *)image->data)[i + 1] = pixels[k].y; + ((float *)image->data)[i + 2] = pixels[k].z; + ((float *)image->data)[i + 3] = pixels[k].w; + } + } break; + default: break; + } + + RL_FREE(pixels); + pixels = NULL; + + // In case original image had mipmaps, generate mipmaps for formated image + // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost + if (image->mipmaps > 1) + { + image->mipmaps = 1; + #if defined(SUPPORT_IMAGE_MANIPULATION) + if (image->data != NULL) ImageMipmaps(image); + #endif + } + } + else TRACELOG(LOG_WARNING, "IMAGE: Data format is compressed, can not be converted"); + } +} + +// Convert image to POT (power-of-two) +// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) +void ImageToPOT(Image *image, Color fill) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Calculate next power-of-two values + // NOTE: Just add the required amount of pixels at the right and bottom sides of image... + int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); + int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); + + // Check if POT texture generation is required (if texture is not already POT) + if ((potWidth != image->width) || (potHeight != image->height)) ImageResizeCanvas(image, potWidth, potHeight, 0, 0, fill); +} + +#if defined(SUPPORT_IMAGE_MANIPULATION) +// Create an image from text (default font) +Image ImageText(const char *text, int fontSize, Color color) +{ + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); + + return imText; +} + +// Create an image from text (custom sprite font) +Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) +{ + int length = (int)strlen(text); + + int textOffsetX = 0; // Image drawing position X + int textOffsetY = 0; // Offset between lines (on line break '\n') + + // NOTE: Text image is generated at font base size, later scaled to desired font size + Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); + + // Create image to store text + Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + + for (int i = 0; i < length; i++) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + + if (codepoint == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (font.baseSize + font.baseSize/2); + textOffsetX = 0; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + Rectangle rec = { (float)(textOffsetX + font.chars[index].offsetX), (float)(textOffsetY + font.chars[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height }; + ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, (float)font.chars[index].image.width, (float)font.chars[index].image.height }, rec, tint); + } + + if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); + else textOffsetX += font.chars[index].advanceX + (int)spacing; + } + + i += (codepointByteCount - 1); // Move text bytes counter to next codepoint + } + + // Scale image depending on text size + if (fontSize > imSize.y) + { + float scaleFactor = fontSize/imSize.y; + TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); + + // Using nearest-neighbor scaling algorithm for default font + if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + } + + return imText; +} + +// Crop image depending on alpha value +// NOTE: Threshold is defined as a percentatge: 0.0f -> 1.0f +void ImageAlphaCrop(Image *image, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Rectangle crop = GetImageAlphaBorder(*image, threshold); + + // Crop if rectangle is valid + if (((int)crop.width != 0) && ((int)crop.height != 0)) ImageCrop(image, crop); +} + +// Clear alpha channel to desired color +// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f +void ImageAlphaClear(Image *image, Color color, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 1; i < image->width*image->height*2; i += 2) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 1] = color.r; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned char thresholdValue = ((threshold < 0.5f)? 0 : 1); + + unsigned char r = (unsigned char)(round((float)color.r*31.0f)); + unsigned char g = (unsigned char)(round((float)color.g*31.0f)); + unsigned char b = (unsigned char)(round((float)color.b*31.0f)); + unsigned char a = (color.a < 128)? 0 : 1; + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0b0000000000000001) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned char thresholdValue = (unsigned char)(threshold*15.0f); + + unsigned char r = (unsigned char)(round((float)color.r*15.0f)); + unsigned char g = (unsigned char)(round((float)color.g*15.0f)); + unsigned char b = (unsigned char)(round((float)color.b*15.0f)); + unsigned char a = (unsigned char)(round((float)color.a*15.0f)); + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0x000f) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 3] = color.r; + ((unsigned char *)image->data)[i - 2] = color.g; + ((unsigned char *)image->data)[i - 1] = color.b; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((float *)image->data)[i] <= threshold) + { + ((float *)image->data)[i - 3] = (float)color.r/255.0f; + ((float *)image->data)[i - 2] = (float)color.g/255.0f; + ((float *)image->data)[i - 1] = (float)color.b/255.0f; + ((float *)image->data)[i] = (float)color.a/255.0f; + } + } + } break; + default: break; + } + } +} + +// Apply alpha mask to image +// NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit) +// NOTE 2: alphaMask should be same size as image +void ImageAlphaMask(Image *image, Image alphaMask) +{ + if ((image->width != alphaMask.width) || (image->height != alphaMask.height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); + } + else if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); + } + else + { + // Force mask to be Grayscale + Image mask = ImageCopy(alphaMask); + if (mask.format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); + + // In case image is only grayscale, we just add alpha channel + if (image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) + { + data[k] = ((unsigned char *)image->data)[i]; + data[k + 1] = ((unsigned char *)mask.data)[i]; + } + + RL_FREE(image->data); + image->data = data; + image->format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + } + else + { + // Convert image to RGBA + if (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) + { + ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i]; + } + } + + UnloadImage(mask); + } +} + +// Premultiply alpha channel +void ImageAlphaPremultiply(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + float alpha = 0.0f; + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + if (pixels[i].a == 0) + { + pixels[i].r = 0; + pixels[i].g = 0; + pixels[i].b = 0; + } + else if (pixels[i].a < 255) + { + alpha = (float)pixels[i].a/255.0f; + pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); + pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); + pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); + } + } + + RL_FREE(image->data); + + int format = image->format; + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Resize and image to new size +// NOTE: Uses stb default scaling filters (both bicubic): +// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) +void ImageResize(Image *image, int newWidth, int newHeight) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + bool fastPath = true; + if ((image->format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) && (image->format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) fastPath = true; + + if (fastPath) + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *output = RL_MALLOC(newWidth*newHeight*bytesPerPixel); + + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 1); break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 2); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 3); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 4); break; + default: break; + } + + RL_FREE(image->data); + image->data = output; + image->width = newWidth; + image->height = newHeight; + } + else + { + // Get data as Color pixels array to work with it + Color *pixels = LoadImageColors(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); + + // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem... + stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4); + + int format = image->format; + + UnloadImageColors(pixels); + RL_FREE(image->data); + + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); // Reformat 32bit RGBA image to original format + } +} + +// Resize and image to new size using Nearest-Neighbor scaling algorithm +void ImageResizeNN(Image *image,int newWidth,int newHeight) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); + + // EDIT: added +1 to account for an early rounding problem + int xRatio = (int)((image->width << 16)/newWidth) + 1; + int yRatio = (int)((image->height << 16)/newHeight) + 1; + + int x2, y2; + for (int y = 0; y < newHeight; y++) + { + for (int x = 0; x < newWidth; x++) + { + x2 = ((x*xRatio) >> 16); + y2 = ((y*yRatio) >> 16); + + output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ; + } + } + + int format = image->format; + + RL_FREE(image->data); + + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); // Reformat 32bit RGBA image to original format + + UnloadImageColors(pixels); +} + +// Resize canvas and fill with color +// NOTE: Resize offset is relative to the top-left corner of the original image +void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else if ((newWidth != image->width) || (newHeight != image->height)) + { + Rectangle srcRec = { 0, 0, (float)image->width, (float)image->height }; + Vector2 dstPos = { (float)offsetX, (float)offsetY }; + + if (offsetX < 0) + { + srcRec.x = (float)-offsetX; + srcRec.width += (float)offsetX; + dstPos.x = 0; + } + else if ((offsetX + image->width) > newWidth) srcRec.width = (float)(newWidth - offsetX); + + if (offsetY < 0) + { + srcRec.y = (float)-offsetY; + srcRec.height += (float)offsetY; + dstPos.y = 0; + } + else if ((offsetY + image->height) > newHeight) srcRec.height = (float)(newHeight - offsetY); + + if (newWidth < srcRec.width) srcRec.width = (float)newWidth; + if (newHeight < srcRec.height) srcRec.height = (float)newHeight; + + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *resizedData = (unsigned char *)RL_CALLOC(newWidth*newHeight*bytesPerPixel, 1); + + // TODO: Fill resizedData with fill color (must be formatted to image->format) + + int dstOffsetSize = ((int)dstPos.y*newWidth + (int)dstPos.x)*bytesPerPixel; + + for (int y = 0; y < (int)srcRec.height; y++) + { + memcpy(resizedData + dstOffsetSize, ((unsigned char *)image->data) + ((y + (int)srcRec.y)*image->width + (int)srcRec.x)*bytesPerPixel, (int)srcRec.width*bytesPerPixel); + dstOffsetSize += (newWidth*bytesPerPixel); + } + + RL_FREE(image->data); + image->data = resizedData; + image->width = newWidth; + image->height = newHeight; + } +} + +// Generate all mipmap levels for a provided image +// NOTE 1: Supports POT and NPOT images +// NOTE 2: image.data is scaled to include mipmap levels +// NOTE 3: Mipmaps format is the same as base image +void ImageMipmaps(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + int mipCount = 1; // Required mipmap levels count (including base level) + int mipWidth = image->width; // Base image width + int mipHeight = image->height; // Base image height + int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); // Image data size (in bytes) + + // Count mipmap levels required + while ((mipWidth != 1) || (mipHeight != 1)) + { + if (mipWidth != 1) mipWidth /= 2; + if (mipHeight != 1) mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + TRACELOGD("IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); + + mipCount++; + mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes) + } + + if (image->mipmaps < mipCount) + { + void *temp = RL_REALLOC(image->data, mipSize); + + if (temp != NULL) image->data = temp; // Assign new pointer (new size) to store mipmaps data + else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps required memory could not be allocated"); + + // Pointer to allocated memory point where store next mipmap level data + unsigned char *nextmip = (unsigned char *)image->data + GetPixelDataSize(image->width, image->height, image->format); + + mipWidth = image->width/2; + mipHeight = image->height/2; + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + Image imCopy = ImageCopy(*image); + + for (int i = 1; i < mipCount; i++) + { + TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); + + ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter + + memcpy(nextmip, imCopy.data, mipSize); + nextmip += mipSize; + image->mipmaps++; + + mipWidth /= 2; + mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + } + + UnloadImage(imCopy); + } + else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps already available"); +} + +// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +// NOTE: In case selected bpp do not represent an known 16bit format, +// dithered data is stored in the LSB part of the unsigned short +void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered"); + return; + } + + if ((rBpp + gBpp + bBpp + aBpp) > 16) + { + TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); + } + else + { + Color *pixels = LoadImageColors(*image); + + RL_FREE(image->data); // free old image data + + if ((image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) + { + TRACELOG(LOG_WARNING, "IMAGE: Format is already 16bpp or lower, dithering could have no effect"); + } + + // Define new image format, check if desired bpp match internal known format + if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + else + { + image->format = 0; + TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); + } + + // NOTE: We will store the dithered data as unsigned short (16bpp) + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + Color oldPixel = WHITE; + Color newPixel = WHITE; + + int rError, gError, bError; + unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition + + #define MIN(a,b) (((a)<(b))?(a):(b)) + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + oldPixel = pixels[y*image->width + x]; + + // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) + newPixel.r = oldPixel.r >> (8 - rBpp); // R bits + newPixel.g = oldPixel.g >> (8 - gBpp); // G bits + newPixel.b = oldPixel.b >> (8 - bBpp); // B bits + newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) + + // NOTE: Error must be computed between new and old pixel but using same number of bits! + // We want to know how much color precision we have lost... + rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); + gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); + bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); + + pixels[y*image->width + x] = newPixel; + + // NOTE: Some cases are out of the array and should be ignored + if (x < (image->width - 1)) + { + pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); + pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); + pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); + } + + if ((x > 0) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff); + } + + if (y < (image->height - 1)) + { + pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); + } + + if ((x < (image->width - 1)) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff); + } + + rPixel = (unsigned short)newPixel.r; + gPixel = (unsigned short)newPixel.g; + bPixel = (unsigned short)newPixel.b; + aPixel = (unsigned short)newPixel.a; + + ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; + } + } + + UnloadImageColors(pixels); + } +} + +// Flip image vertically +void ImageFlipVertical(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int i = (image->height - 1), offsetSize = 0; i >= 0; i--) + { + memcpy(flippedData + offsetSize, ((unsigned char *)image->data) + i*image->width*bytesPerPixel, image->width*bytesPerPixel); + offsetSize += image->width*bytesPerPixel; + } + + RL_FREE(image->data); + image->data = flippedData; + } +} + +// Flip image horizontally +void ImageFlipHorizontal(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + // OPTION 1: Move pixels with memcopy() + //memcpy(flippedData + (y*image->width + x)*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - 1 - x))*bytesPerPixel, bytesPerPixel); + + // OPTION 2: Just copy data pixel by pixel + for (int i = 0; i < bytesPerPixel; i++) flippedData[(y*image->width + x)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - 1 - x))*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = flippedData; + + /* + // OPTION 3: Faster implementation (specific for 32bit pixels) + // NOTE: It does not require additional allocations + uint32_t *ptr = (uint32_t *)image->data; + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width/2; x++) + { + uint32_t backup = ptr[y*image->width + x]; + ptr[y*image->width + x] = ptr[y*image->width + (image->width - 1 - x)]; + ptr[y*image->width + (image->width - 1 - x)] = backup; + } + } + */ + } +} + +// Rotate image clockwise 90deg +void ImageRotateCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + (image->height - y - 1))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + (image->height - y - 1))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; + + image->width = height; + image->height = width; + } +} + +// Rotate image counter-clockwise 90deg +void ImageRotateCCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + y))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - x - 1))*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + y)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - x - 1))*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; + + image->width = height; + image->height = width; + } +} + +// Modify image color: tint +void ImageColorTint(Image *image, Color color) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + float cR = (float)color.r/255; + float cG = (float)color.g/255; + float cB = (float)color.b/255; + float cA = (float)color.a/255; + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + int index = y*image->width + x; + unsigned char r = (unsigned char)(((float)pixels[index].r/255*cR)*255.0f); + unsigned char g = (unsigned char)(((float)pixels[index].g/255*cG)*255.0f); + unsigned char b = (unsigned char)(((float)pixels[index].b/255*cB)*255.0f); + unsigned char a = (unsigned char)(((float)pixels[index].a/255*cA)*255.0f); + + pixels[y*image->width + x].r = r; + pixels[y*image->width + x].g = g; + pixels[y*image->width + x].b = b; + pixels[y*image->width + x].a = a; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: invert +void ImageColorInvert(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + pixels[y*image->width + x].r = 255 - pixels[y*image->width + x].r; + pixels[y*image->width + x].g = 255 - pixels[y*image->width + x].g; + pixels[y*image->width + x].b = 255 - pixels[y*image->width + x].b; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: grayscale +void ImageColorGrayscale(Image *image) +{ + ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); +} + +// Modify image color: contrast +// NOTE: Contrast values between -100 and 100 +void ImageColorContrast(Image *image, float contrast) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (contrast < -100) contrast = -100; + if (contrast > 100) contrast = 100; + + contrast = (100.0f + contrast)/100.0f; + contrast *= contrast; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + float pR = (float)pixels[y*image->width + x].r/255.0f; + pR -= 0.5; + pR *= contrast; + pR += 0.5; + pR *= 255; + if (pR < 0) pR = 0; + if (pR > 255) pR = 255; + + float pG = (float)pixels[y*image->width + x].g/255.0f; + pG -= 0.5; + pG *= contrast; + pG += 0.5; + pG *= 255; + if (pG < 0) pG = 0; + if (pG > 255) pG = 255; + + float pB = (float)pixels[y*image->width + x].b/255.0f; + pB -= 0.5; + pB *= contrast; + pB += 0.5; + pB *= 255; + if (pB < 0) pB = 0; + if (pB > 255) pB = 255; + + pixels[y*image->width + x].r = (unsigned char)pR; + pixels[y*image->width + x].g = (unsigned char)pG; + pixels[y*image->width + x].b = (unsigned char)pB; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: brightness +// NOTE: Brightness values between -255 and 255 +void ImageColorBrightness(Image *image, int brightness) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (brightness < -255) brightness = -255; + if (brightness > 255) brightness = 255; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + int cR = pixels[y*image->width + x].r + brightness; + int cG = pixels[y*image->width + x].g + brightness; + int cB = pixels[y*image->width + x].b + brightness; + + if (cR < 0) cR = 1; + if (cR > 255) cR = 255; + + if (cG < 0) cG = 1; + if (cG > 255) cG = 255; + + if (cB < 0) cB = 1; + if (cB > 255) cB = 255; + + pixels[y*image->width + x].r = (unsigned char)cR; + pixels[y*image->width + x].g = (unsigned char)cG; + pixels[y*image->width + x].b = (unsigned char)cB; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: replace color +void ImageColorReplace(Image *image, Color color, Color replace) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + if ((pixels[y*image->width + x].r == color.r) && + (pixels[y*image->width + x].g == color.g) && + (pixels[y*image->width + x].b == color.b) && + (pixels[y*image->width + x].a == color.a)) + { + pixels[y*image->width + x].r = replace.r; + pixels[y*image->width + x].g = replace.g; + pixels[y*image->width + x].b = replace.b; + pixels[y*image->width + x].a = replace.a; + } + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} +#endif // SUPPORT_IMAGE_MANIPULATION + +// Load color data from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImageColors(); +Color *LoadImageColors(Image image) +{ + if ((image.width == 0) || (image.height == 0)) return NULL; + + Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + if ((image.format == PIXELFORMAT_UNCOMPRESSED_R32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); + + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].r = ((unsigned char *)image.data)[i]; + pixels[i].g = ((unsigned char *)image.data)[i]; + pixels[i].b = ((unsigned char *)image.data)[i]; + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = ((unsigned char *)image.data)[k + 1]; + + k += 2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); + pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); + pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); + pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k + 1]; + pixels[i].b = ((unsigned char *)image.data)[k + 2]; + pixels[i].a = ((unsigned char *)image.data)[k + 3]; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; + pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; + pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); + + k += 4; + } break; + default: break; + } + } + } + + return pixels; +} + +// Load colors palette from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImagePalette() +Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorsCount) +{ + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + int palCount = 0; + Color *palette = NULL; + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) + { + palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); + + for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + + for (int i = 0; i < image.width*image.height; i++) + { + if (pixels[i].a > 0) + { + bool colorInPalette = false; + + // Check if the color is already on palette + for (int j = 0; j < maxPaletteSize; j++) + { + if (COLOR_EQUAL(pixels[i], palette[j])) + { + colorInPalette = true; + break; + } + } + + // Store color if not on the palette + if (!colorInPalette) + { + palette[palCount] = pixels[i]; // Add pixels[i] to palette + palCount++; + + // We reached the limit of colors supported by palette + if (palCount >= maxPaletteSize) + { + i = image.width*image.height; // Finish palette get + TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); + } + } + } + } + + UnloadImageColors(pixels); + } + + *colorsCount = palCount; + + return palette; +} + +// Unload color data loaded with LoadImageColors() +void UnloadImageColors(Color *colors) +{ + RL_FREE(colors); +} + +// Unload colors palette loaded with LoadImagePalette() +void UnloadImagePalette(Color *colors) +{ + RL_FREE(colors); +} + +// Get pixel data from image as Vector4 array (float normalized) +static Vector4 *LoadImageDataNormalized(Image image) +{ + Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; + + k += 2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); + pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); + pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); + pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); + pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); + pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); + pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = 0.0f; + pixels[i].z = 0.0f; + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = ((float *)image.data)[k + 3]; + + k += 4; + } + default: break; + } + } + } + + return pixels; +} + +// Get image alpha border rectangle +// NOTE: Threshold is defined as a percentatge: 0.0f -> 1.0f +Rectangle GetImageAlphaBorder(Image image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) + { + int xMin = 65536; // Define a big enough number + int xMax = 0; + int yMin = 65536; + int yMax = 0; + + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) + { + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + } + + // Check for empty blank image + if ((xMin != 65536) && (xMax != 65536)) + { + crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; + } + + UnloadImageColors(pixels); + } + + return crop; +} + +//------------------------------------------------------------------------------------ +// Image drawing functions +//------------------------------------------------------------------------------------ +// Clear image background with given color +void ImageClearBackground(Image *dst, Color color) +{ + for (int i = 0; i < dst->width*dst->height; ++i) ImageDrawPixel(dst, i%dst->width, i/dst->width, color); +} + +// Draw pixel within an image +// NOTE: Compressed image formats not supported +void ImageDrawPixel(Image *dst, int x, int y, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; + + switch (dst->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[y*dst->width + x] = gray; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; + ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // NOTE: Calculate grayscale equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; + ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; + + } break; + default: break; + } +} + +// Draw pixel within an image (Vector version) +void ImageDrawPixelV(Image *dst, Vector2 position, Color color) +{ + ImageDrawPixel(dst, (int)position.x, (int)position.y, color); +} + +// Draw line within an image +void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + int m = 2*(endPosY - startPosY); + int slopeError = m - (endPosX - startPosX); + + for (int x = startPosX, y = startPosY; x <= endPosX; x++) + { + ImageDrawPixel(dst, x, y, color); + slopeError += m; + + if (slopeError >= 0) + { + y++; + slopeError -= 2*(endPosX - startPosX); + } + } +} + +// Draw line within an image (Vector version) +void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) +{ + ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color); +} + +// Draw circle within an image +void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) +{ + int x = 0, y = radius; + int decesionParameter = 3 - 2*radius; + + while (y >= x) + { + ImageDrawPixel(dst, centerX + x, centerY + y, color); + ImageDrawPixel(dst, centerX - x, centerY + y, color); + ImageDrawPixel(dst, centerX + x, centerY - y, color); + ImageDrawPixel(dst, centerX - x, centerY - y, color); + ImageDrawPixel(dst, centerX + y, centerY + x, color); + ImageDrawPixel(dst, centerX - y, centerY + x, color); + ImageDrawPixel(dst, centerX + y, centerY - x, color); + ImageDrawPixel(dst, centerX - y, centerY - x, color); + x++; + + if (decesionParameter > 0) + { + y--; + decesionParameter = decesionParameter + 4*(x - y) + 10; + } + else decesionParameter = decesionParameter + 4*x + 6; + } +} + +// Draw circle within an image (Vector version) +void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) +{ + ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); +} + +// Draw rectangle within an image +void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) +{ + ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); +} + +// Draw rectangle within an image (Vector version) +void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) +{ + ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); +} + +// Draw rectangle within an image +void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; + + int sy = (int)rec.y; + int ey = sy + (int)rec.height; + + int sx = (int)rec.x; + int ex = sx + (int)rec.width; + + for (int y = sy; y < ey; y++) + { + for (int x = sx; x < ex; x++) + { + ImageDrawPixel(dst, x, y, color); + } + } +} + +// Draw rectangle lines within an image +void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) +{ + ImageDrawRectangle(dst, (int)rec.x, (int)rec.y, (int)rec.width, thick, color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)(rec.x + rec.width - thick), (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + rec.height - thick), (int)rec.width, thick, color); +} + +// Draw an image (source) within an image (destination) +// NOTE: Color tint is applied to source image +void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || + (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; + + if (dst->mipmaps > 1) TRACELOG(LOG_WARNING, "Image drawing only applied to base mipmap level"); + if (dst->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); + else + { + Image srcMod = { 0 }; // Source copy (in case it was required) + Image *srcPtr = &src; // Pointer to source image + bool useSrcMod = false; // Track source copy required + + // Source rectangle out-of-bounds security checks + if (srcRec.x < 0) { srcRec.width += srcRec.x; srcRec.x = 0; } + if (srcRec.y < 0) { srcRec.height += srcRec.y; srcRec.y = 0; } + if ((srcRec.x + srcRec.width) > src.width) srcRec.width = src.width - srcRec.x; + if ((srcRec.y + srcRec.height) > src.height) srcRec.height = src.height - srcRec.y; + + // Check if source rectangle needs to be resized to destination rectangle + // In that case, we make a copy of source and we apply all required transform + if (((int)srcRec.width != (int)dstRec.width) || ((int)srcRec.height != (int)dstRec.height)) + { + srcMod = ImageFromImage(src, srcRec); // Create image from another image + ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); // Resize to destination rectangle + srcRec = (Rectangle){ 0, 0, (float)srcMod.width, (float)srcMod.height }; + + srcPtr = &srcMod; + useSrcMod = true; + } + + // Destination rectangle out-of-bounds security checks + if (dstRec.x < 0) + { + srcRec.x = -dstRec.x; + srcRec.width += dstRec.x; + dstRec.x = 0; + } + else if ((dstRec.x + srcRec.width) > dst->width) srcRec.width = dst->width - dstRec.x; + + if (dstRec.y < 0) + { + srcRec.y = -dstRec.y; + srcRec.height += dstRec.y; + dstRec.y = 0; + } + else if ((dstRec.y + srcRec.height) > dst->height) srcRec.height = dst->height - dstRec.y; + + if (dst->width < srcRec.width) srcRec.width = (float)dst->width; + if (dst->height < srcRec.height) srcRec.height = (float)dst->height; + + // This blitting method is quite fast! The process followed is: + // for every pixel -> [get_src_format/get_dst_format -> blend -> format_to_dst] + // Some optimization ideas: + // [x] Avoid creating source copy if not required (no resize required) + // [x] Optimize ImageResize() for pixel format (alternative: ImageResizeNN()) + // [x] Optimize ColorAlphaBlend() to avoid processing (alpha = 0) and (alpha = 1) + // [x] Optimize ColorAlphaBlend() for faster operations (maybe avoiding divs?) + // [x] Consider fast path: no alpha blending required cases (src has no alpha) + // [x] Consider fast path: same src/dst format with no alpha -> direct line copy + // [-] GetPixelColor(): Return Vector4 instead of Color, easier for ColorAlphaBlend() + + Color colSrc, colDst, blend; + bool blendRequired = true; + + // Fast path: Avoid blend if source has no alpha to blend + if ((tint.a == 255) && ((srcPtr->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R5G6B5))) blendRequired = false; + + int strideDst = GetPixelDataSize(dst->width, 1, dst->format); + int bytesPerPixelDst = strideDst/(dst->width); + + int strideSrc = GetPixelDataSize(srcPtr->width, 1, srcPtr->format); + int bytesPerPixelSrc = strideSrc/(srcPtr->width); + + unsigned char *pSrcBase = (unsigned char *)srcPtr->data + ((int)srcRec.y*srcPtr->width + (int)srcRec.x)*bytesPerPixelSrc; + unsigned char *pDstBase = (unsigned char *)dst->data + ((int)dstRec.y*dst->width + (int)dstRec.x)*bytesPerPixelDst; + + for (int y = 0; y < (int)srcRec.height; y++) + { + unsigned char *pSrc = pSrcBase; + unsigned char *pDst = pDstBase; + + // Fast path: Avoid moving pixel by pixel if no blend required and same format + if (!blendRequired && (srcPtr->format == dst->format)) memcpy(pDst, pSrc, (int)(srcRec.width)*bytesPerPixelSrc); + else + { + for (int x = 0; x < (int)srcRec.width; x++) + { + colSrc = GetPixelColor(pSrc, srcPtr->format); + colDst = GetPixelColor(pDst, dst->format); + + // Fast path: Avoid blend if source has no alpha to blend + if (blendRequired) blend = ColorAlphaBlend(colDst, colSrc, tint); + else blend = colSrc; + + SetPixelColor(pDst, blend, dst->format); + + pDst += bytesPerPixelDst; + pSrc += bytesPerPixelSrc; + } + } + + pSrcBase += strideSrc; + pDstBase += strideDst; + } + + if (useSrcMod) UnloadImage(srcMod); // Unload source modified image + } +} + +// Draw text (default font) within an image (destination) +void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) +{ + Vector2 position = { (float)posX, (float)posY }; + + // NOTE: For default font, sapcing is set to desired font size / default font size (10) + ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color); +} + +// Draw text (custom sprite font) within an image (destination) +void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) +{ + Image imText = ImageTextEx(font, text, fontSize, spacing, tint); + + Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; + Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; + + ImageDraw(dst, imText, srcRec, dstRec, WHITE); + + UnloadImage(imText); +} + +//------------------------------------------------------------------------------------ +// Texture loading functions +//------------------------------------------------------------------------------------ +// Load texture from file into GPU memory (VRAM) +Texture2D LoadTexture(const char *fileName) +{ + Texture2D texture = { 0 }; + + Image image = LoadImage(fileName); + + if (image.data != NULL) + { + texture = LoadTextureFromImage(image); + UnloadImage(image); + } + + return texture; +} + +// Load a texture from image data +// NOTE: image is not unloaded, it must be done manually +Texture2D LoadTextureFromImage(Image image) +{ + Texture2D texture = { 0 }; + + if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) + { + texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); + } + else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); + + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; + + return texture; +} + +// Load cubemap from image, multiple image cubemap layouts supported +TextureCubemap LoadTextureCubemap(Image image, int layout) +{ + TextureCubemap cubemap = { 0 }; + + if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type + { + // Check image width/height to determine the type of cubemap provided + if (image.width > image.height) + { + if ((image.width/6) == image.height) { layout = CUBEMAP_LAYOUT_LINE_HORIZONTAL; cubemap.width = image.width/6; } + else if ((image.width/4) == (image.height/3)) { layout = CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } + else if (image.width >= (int)((float)image.height*1.85f)) { layout = CUBEMAP_LAYOUT_PANORAMA; cubemap.width = image.width/4; } + } + else if (image.height > image.width) + { + if ((image.height/6) == image.width) { layout = CUBEMAP_LAYOUT_LINE_VERTICAL; cubemap.width = image.height/6; } + else if ((image.width/3) == (image.height/4)) { layout = CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } + } + + cubemap.height = cubemap.width; + } + + if (layout != CUBEMAP_LAYOUT_AUTO_DETECT) + { + int size = cubemap.width; + + Image faces = { 0 }; // Vertical column image + Rectangle faceRecs[6] = { 0 }; // Face source rectangles + for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; + + if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) + { + faces = image; + for (int i = 0; i < 6; i++) faceRecs[i].y = (float)size*i; + } + else if (layout == CUBEMAP_LAYOUT_PANORAMA) + { + // TODO: Convert panorama image to square faces... + // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp + } + else + { + if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; + else if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) + { + faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; + faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = 0; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; + } + else if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) + { + faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; + faceRecs[1].x = 0; faceRecs[1].y = (float)size; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = (float)size; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*3; faceRecs[5].y = (float)size; + } + + // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + faces = GenImageColor(size, size*6, MAGENTA); + ImageFormat(&faces, image.format); + + // TODO: Image formating does not work with compressed textures! + } + + for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); + + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); + if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); + + UnloadImage(faces); + } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); + + return cubemap; +} + +// Load texture for rendering (framebuffer) +// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer +RenderTexture2D LoadRenderTexture(int width, int height) +{ + RenderTexture2D target = { 0 }; + + target.id = rlLoadFramebuffer(width, height); // Load an empty framebuffer + + if (target.id > 0) + { + rlEnableFramebuffer(target.id); + + // Create color texture (default to RGBA) + target.texture.id = rlLoadTexture(NULL, width, height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + target.texture.width = width; + target.texture.height = height; + target.texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + target.texture.mipmaps = 1; + + // Create depth renderbuffer/texture + target.depth.id = rlLoadTextureDepth(width, height, true); + target.depth.width = width; + target.depth.height = height; + target.depth.format = 19; //DEPTH_COMPONENT_24BIT? + target.depth.mipmaps = 1; + + // Attach color texture and depth renderbuffer/texture to FBO + rlFramebufferAttach(target.id, target.texture.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(target.id, target.depth.id, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); + + // Check if fbo is complete with attachments (valid) + if (rlFramebufferComplete(target.id)) TRACELOG(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", target.id); + + rlDisableFramebuffer(); + } + else TRACELOG(LOG_WARNING, "FBO: Framebuffer object can not be created"); + + return target; +} + +// Unload texture from GPU memory (VRAM) +void UnloadTexture(Texture2D texture) +{ + if (texture.id > 0) + { + rlUnloadTexture(texture.id); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + } +} + +// Unload render texture from GPU memory (VRAM) +void UnloadRenderTexture(RenderTexture2D target) +{ + if (target.id > 0) + { + // Color texture attached to FBO is deleted + rlUnloadTexture(target.texture.id); + + // NOTE: Depth texture/renderbuffer is automatically + // queried and deleted before deleting framebuffer + rlUnloadFramebuffer(target.id); + } +} + +// Update GPU texture with new data +// NOTE: pixels data must match texture.format +void UpdateTexture(Texture2D texture, const void *pixels) +{ + rlUpdateTexture(texture.id, 0, 0, texture.width, texture.height, texture.format, pixels); +} + +// Update GPU texture rectangle with new data +// NOTE: pixels data must match texture.format +void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels) +{ + rlUpdateTexture(texture.id, (int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, texture.format, pixels); +} + +// Get pixel data from GPU texture and return an Image +// NOTE: Compressed texture formats not supported +Image GetTextureData(Texture2D texture) +{ + Image image = { 0 }; + + if (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + image.data = rlReadTexturePixels(texture); + + if (image.data != NULL) + { + image.width = texture.width; + image.height = texture.height; + image.format = texture.format; + image.mipmaps = 1; + +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, + // coming from FBO color buffer attachment, but it seems + // original texture format is retrieved on RPI... + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; +#endif + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); + + return image; +} + +// Get pixel data from GPU frontbuffer and return an Image (screenshot) +Image GetScreenData(void) +{ + Image image = { 0 }; + + image.width = GetScreenWidth(); + image.height = GetScreenHeight(); + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + image.data = rlReadScreenPixels(image.width, image.height); + + return image; +} + +//------------------------------------------------------------------------------------ +// Texture configuration functions +//------------------------------------------------------------------------------------ +// Generate GPU mipmaps for a texture +void GenTextureMipmaps(Texture2D *texture) +{ + // NOTE: NPOT textures support check inside function + // On WebGL (OpenGL ES 2.0) NPOT textures support is limited + rlGenerateMipmaps(texture); +} + +// Set texture scaling filter mode +void SetTextureFilter(Texture2D texture, int filter) +{ + switch (filter) + { + case TEXTURE_FILTER_POINT: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_NEAREST); + + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); + } + else + { + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_NEAREST); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); + } + } break; + case TEXTURE_FILTER_BILINEAR: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) + // Alternative: RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + else + { + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + } break; + case TEXTURE_FILTER_TRILINEAR: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_LINEAR); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + else + { + TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] No mipmaps available for TRILINEAR texture filtering", texture.id); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + } break; + case TEXTURE_FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 4); break; + case TEXTURE_FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 8); break; + case TEXTURE_FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 16); break; + default: break; + } +} + +// Set texture wrapping mode +void SetTextureWrap(Texture2D texture, int wrap) +{ + switch (wrap) + { + case TEXTURE_WRAP_REPEAT: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT); + } break; + case TEXTURE_WRAP_CLAMP: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_CLAMP); + } break; + case TEXTURE_WRAP_MIRROR_REPEAT: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_REPEAT); + } break; + case TEXTURE_WRAP_MIRROR_CLAMP: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_CLAMP); + } break; + default: break; + } +} + +//------------------------------------------------------------------------------------ +// Texture drawing functions +//------------------------------------------------------------------------------------ +// Draw a Texture2D +void DrawTexture(Texture2D texture, int posX, int posY, Color tint) +{ + DrawTextureEx(texture, (Vector2){ (float)posX, (float)posY }, 0.0f, 1.0f, tint); +} + +// Draw a Texture2D with position defined as Vector2 +void DrawTextureV(Texture2D texture, Vector2 position, Color tint) +{ + DrawTextureEx(texture, position, 0, 1.0f, tint); +} + +// Draw a Texture2D with extended parameters +void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint) +{ + Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + Rectangle dest = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, source, dest, origin, rotation, tint); +} + +// Draw a part of a texture (defined by a rectangle) +void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint) +{ + Rectangle dest = { position.x, position.y, fabsf(source.width), fabsf(source.height) }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, source, dest, origin, 0.0f, tint); +} + +// Draw texture quad with tiling and offset parameters +// NOTE: Tiling and offset should be provided considering normalized texture values [0..1] +// i.e tiling = { 1.0f, 1.0f } refers to all texture, offset = { 0.5f, 0.5f } moves texture origin to center +void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint) +{ + Rectangle source = { offset.x*texture.width, offset.y*texture.height, tiling.x*texture.width, tiling.y*texture.height }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, source, quad, origin, 0.0f, tint); +} + +// Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +// NOTE: For tilling a whole texture DrawTextureQuad() is better +void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint) +{ + if ((texture.id <= 0) || (scale <= 0.0f)) return; // Wanna see a infinite loop?!...just delete this line! + + int tileWidth = (int)(source.width*scale), tileHeight = (int)(source.height*scale); + if ((dest.width < tileWidth) && (dest.height < tileHeight)) + { + // Can fit only one tile + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y, dest.width, dest.height}, origin, rotation, tint); + } + else if (dest.width <= tileWidth) + { + // Tiled vertically (one column) + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, source.height}, (Rectangle){dest.x, dest.y + dy, dest.width, (float)tileHeight}, origin, rotation, tint); + } + + // Fit last tile + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y + dy, dest.width, dest.height - dy}, origin, rotation, tint); + } + } + else if (dest.height <= tileHeight) + { + // Tiled horizontally (one row) + int dx = 0; + for (;dx+tileWidth < dest.width; dx += tileWidth) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)dest.height/tileHeight)*source.height}, (Rectangle){dest.x + dx, dest.y, (float)tileWidth, dest.height}, origin, rotation, tint); + } + + // Fit last tile + if (dx < dest.width) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y, dest.width - dx, dest.height}, origin, rotation, tint); + } + } + else + { + // Tiled both horizontally and vertically (rows and columns) + int dx = 0; + for (;dx+tileWidth < dest.width; dx += tileWidth) + { + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, source, (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, (float)tileHeight}, origin, rotation, tint); + } + + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, dest.height - dy}, origin, rotation, tint); + } + } + + // Fit last column of tiles + if (dx < dest.width) + { + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, (float)tileHeight}, origin, rotation, tint); + } + + // Draw final tile in the bottom right corner + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, dest.height - dy}, origin, rotation, tint); + } + } + } +} + +// Draw a part of a texture (defined by a rectangle) with 'pro' parameters +// NOTE: origin is relative to destination rectangle size +void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint) +{ + // Check if texture is valid + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + bool flipX = false; + + if (source.width < 0) { flipX = true; source.width *= -1; } + if (source.height < 0) source.y -= source.height; + + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = dest.x - origin.x; + float y = dest.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + dest.width, y }; + bottomLeft = (Vector2){ x, y + dest.height }; + bottomRight = (Vector2){ x + dest.width, y + dest.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = dest.x; + float y = dest.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + dest.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + dest.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + dest.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + dest.height)*cosRotation; + + bottomRight.x = x + (dx + dest.width)*cosRotation - (dy + dest.height)*sinRotation; + bottomRight.y = y + (dx + dest.width)*sinRotation + (dy + dest.height)*cosRotation; + } + + rlCheckRenderBatchLimit(4); // Make sure there is enough free space on the batch buffer + + rlSetTexture(texture.id); + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); + rlVertex2f(topLeft.x, topLeft.y); + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(bottomRight.x, bottomRight.y); + + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + rlSetTexture(0); + + // NOTE: Vertex position can be transformed using matrices + // but the process is way more costly than just calculating + // the vertex positions manually, like done above. + // I leave here the old implementation for educational pourposes, + // just in case someone wants to do some performance test + /* + rlSetTexture(texture.id); + rlPushMatrix(); + rlTranslatef(dest.x, dest.y, 0.0f); + if (rotation != 0.0f) rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); + rlVertex2f(0.0f, 0.0f); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(0.0f, dest.height); + + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(dest.width, dest.height); + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(dest.width, 0.0f); + rlEnd(); + rlPopMatrix(); + rlSetTexture(0); + */ + } +} + +// Draws a texture (or part of it) that stretches or shrinks nicely using n-patch info +void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint) +{ + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + float patchWidth = (dest.width <= 0.0f)? 0.0f : dest.width; + float patchHeight = (dest.height <= 0.0f)? 0.0f : dest.height; + + if (nPatchInfo.source.width < 0) nPatchInfo.source.x -= nPatchInfo.source.width; + if (nPatchInfo.source.height < 0) nPatchInfo.source.y -= nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) patchHeight = nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) patchWidth = nPatchInfo.source.width; + + bool drawCenter = true; + bool drawMiddle = true; + float leftBorder = (float)nPatchInfo.left; + float topBorder = (float)nPatchInfo.top; + float rightBorder = (float)nPatchInfo.right; + float bottomBorder = (float)nPatchInfo.bottom; + + // adjust the lateral (left and right) border widths in case patchWidth < texture.width + if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_VERTICAL) + { + drawCenter = false; + leftBorder = (leftBorder/(leftBorder + rightBorder))*patchWidth; + rightBorder = patchWidth - leftBorder; + } + // adjust the lateral (top and bottom) border heights in case patchHeight < texture.height + if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_HORIZONTAL) + { + drawMiddle = false; + topBorder = (topBorder/(topBorder + bottomBorder))*patchHeight; + bottomBorder = patchHeight - topBorder; + } + + Vector2 vertA, vertB, vertC, vertD; + vertA.x = 0.0f; // outer left + vertA.y = 0.0f; // outer top + vertB.x = leftBorder; // inner left + vertB.y = topBorder; // inner top + vertC.x = patchWidth - rightBorder; // inner right + vertC.y = patchHeight - bottomBorder; // inner bottom + vertD.x = patchWidth; // outer right + vertD.y = patchHeight; // outer bottom + + Vector2 coordA, coordB, coordC, coordD; + coordA.x = nPatchInfo.source.x/width; + coordA.y = nPatchInfo.source.y/height; + coordB.x = (nPatchInfo.source.x + leftBorder)/width; + coordB.y = (nPatchInfo.source.y + topBorder)/height; + coordC.x = (nPatchInfo.source.x + nPatchInfo.source.width - rightBorder)/width; + coordC.y = (nPatchInfo.source.y + nPatchInfo.source.height - bottomBorder)/height; + coordD.x = (nPatchInfo.source.x + nPatchInfo.source.width)/width; + coordD.y = (nPatchInfo.source.y + nPatchInfo.source.height)/height; + + rlSetTexture(texture.id); + + rlPushMatrix(); + rlTranslatef(dest.x, dest.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + if (nPatchInfo.layout == NPATCH_NINE_PATCH) + { + // ------------------------------------------------------------ + // TOP-LEFT QUAD + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // TOP-CENTER QUAD + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad + } + // TOP-RIGHT QUAD + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad + if (drawMiddle) + { + // ------------------------------------------------------------ + // MIDDLE-LEFT QUAD + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad + if (drawCenter) + { + // MIDDLE-CENTER QUAD + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-left corner for texture and quad + } + + // MIDDLE-RIGHT QUAD + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-left corner for texture and quad + } + + // ------------------------------------------------------------ + // BOTTOM-LEFT QUAD + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad + if (drawCenter) + { + // BOTTOM-CENTER QUAD + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-left corner for texture and quad + } + + // BOTTOM-RIGHT QUAD + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-left corner for texture and quad + } + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) + { + // TOP QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // MIDDLE QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad + } + // BOTTOM QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad + } + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) + { + // LEFT QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // CENTER QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad + } + // RIGHT QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad + } + rlEnd(); + rlPopMatrix(); + + rlSetTexture(0); + } +} + +// Draw textured polygon, defined by vertex and texturecoordinates +// NOTE: Polygon center must have straight line path to all points +// without crossing perimeter, points must be in anticlockwise order +void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointsCount, Color tint) +{ + rlCheckRenderBatchLimit((pointsCount - 1)*4); + + rlSetTexture(texture.id); + + // Texturing is only supported on QUADs + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + + for (int i = 0; i < pointsCount - 1; i++) + { + rlTexCoord2f(0.5f, 0.5f); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texcoords[i].x, texcoords[i].y); + rlVertex2f(points[i].x + center.x, points[i].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + } + rlEnd(); + + rlSetTexture(0); +} + +// Returns color with alpha applied, alpha goes from 0.0f to 1.0f +Color Fade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; +} + +// Returns hexadecimal value for a Color +int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Returns color normalized as float [0..1] +Vector4 ColorNormalize(Color color) +{ + Vector4 result; + + result.x = (float)color.r/255.0f; + result.y = (float)color.g/255.0f; + result.z = (float)color.b/255.0f; + result.w = (float)color.a/255.0f; + + return result; +} + +// Returns color from normalized values [0..1] +Color ColorFromNormalized(Vector4 normalized) +{ + Color result; + + result.r = (unsigned char)(normalized.x*255.0f); + result.g = (unsigned char)(normalized.y*255.0f); + result.b = (unsigned char)(normalized.z*255.0f); + result.a = (unsigned char)(normalized.w*255.0f); + + return result; +} + +// Returns HSV values for a Color +// NOTE: Hue is returned as degrees [0..360] +Vector3 ColorToHSV(Color color) +{ + Vector3 hsv = { 0 }; + Vector3 rgb = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + float min, max, delta; + + min = rgb.x < rgb.y? rgb.x : rgb.y; + min = min < rgb.z? min : rgb.z; + + max = rgb.x > rgb.y? rgb.x : rgb.y; + max = max > rgb.z? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = NAN; // Undefined + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Returns a Color from HSV values +// Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion +// NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors +// Hue is provided in degrees: [0..360] +// Saturation/Value are provided normalized: [0.0f..1.0f] +Color ColorFromHSV(float hue, float saturation, float value) +{ + Color color = { 0, 0, 0, 255 }; + + // Red channel + float k = fmodf((5.0f + hue/60.0f), 6); + float t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.r = (unsigned char)((value - value*saturation*k)*255.0f); + + // Green channel + k = fmodf((3.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.g = (unsigned char)((value - value*saturation*k)*255.0f); + + // Blue channel + k = fmodf((1.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.b = (unsigned char)((value - value*saturation*k)*255.0f); + + return color; +} + +// Returns color with alpha applied, alpha goes from 0.0f to 1.0f +Color ColorAlpha(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; +} + +// Returns src alpha-blended into dst color with tint +Color ColorAlphaBlend(Color dst, Color src, Color tint) +{ + Color out = WHITE; + + // Apply color tint to source color + src.r = (unsigned char)(((unsigned int)src.r*(unsigned int)tint.r) >> 8); + src.g = (unsigned char)(((unsigned int)src.g*(unsigned int)tint.g) >> 8); + src.b = (unsigned char)(((unsigned int)src.b*(unsigned int)tint.b) >> 8); + src.a = (unsigned char)(((unsigned int)src.a*(unsigned int)tint.a) >> 8); + +//#define COLORALPHABLEND_FLOAT +#define COLORALPHABLEND_INTEGERS +#if defined(COLORALPHABLEND_INTEGERS) + if (src.a == 0) out = dst; + else if (src.a == 255) out = src; + else + { + unsigned int alpha = (unsigned int)src.a + 1; // We are shifting by 8 (dividing by 256), so we need to take that excess into account + out.a = (unsigned char)(((unsigned int)alpha*256 + (unsigned int)dst.a*(256 - alpha)) >> 8); + + if (out.a > 0) + { + out.r = (unsigned char)((((unsigned int)src.r*alpha*256 + (unsigned int)dst.r*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + out.g = (unsigned char)((((unsigned int)src.g*alpha*256 + (unsigned int)dst.g*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + out.b = (unsigned char)((((unsigned int)src.b*alpha*256 + (unsigned int)dst.b*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + } + } +#endif +#if defined(COLORALPHABLEND_FLOAT) + if (src.a == 0) out = dst; + else if (src.a == 255) out = src; + else + { + Vector4 fdst = ColorNormalize(dst); + Vector4 fsrc = ColorNormalize(src); + Vector4 ftint = ColorNormalize(tint); + Vector4 fout = { 0 }; + + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); + + if (fout.w > 0.0f) + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } + + out = (Color){ (unsigned char)(fout.x*255.0f), (unsigned char)(fout.y*255.0f), (unsigned char)(fout.z*255.0f), (unsigned char)(fout.w*255.0f) }; + } +#endif + + return out; +} + +// Returns a Color struct from hexadecimal value +Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Get color from a pixel from certain format +Color GetPixelColor(void *srcPtr, int format) +{ + Color col = { 0 }; + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 5) & 0b0000000000111111)*255/63); + col.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); + col.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 6) & 0b0000000000011111)*255/31); + col.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); + col.a = (((unsigned short *)srcPtr)[0] & 0b0000000000000001)? 255 : 0; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 12)*255/15); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 8) & 0b0000000000001111)*255/15); + col.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 4) & 0b0000000000001111)*255/15); + col.a = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000001111)*255/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: break; + default: break; + } + + return col; +} + +// Set pixel color formatted into destination pointer +void SetPixelColor(void *dstPtr, Color color, int format) +{ + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + ((unsigned char *)dstPtr)[1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + ((unsigned char *)dstPtr)[3] = color.a; + + } break; + default: break; + } +} + +// Get pixel data size in bytes for certain format +// NOTE: Size can be requested for Image or Texture data +int GetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_DDS) +// Loading DDS image data (compressed or uncompressed) +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extension: + // GL_EXT_texture_compression_s3tc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 + // GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + // GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + + #define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII + #define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII + #define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII + + // DDS Pixel Format + typedef struct { + unsigned int size; + unsigned int flags; + unsigned int fourCC; + unsigned int rgbBitCount; + unsigned int rBitMask; + unsigned int gBitMask; + unsigned int bBitMask; + unsigned int aBitMask; + } DDSPixelFormat; + + // DDS Header (124 bytes) + typedef struct { + unsigned int size; + unsigned int flags; + unsigned int height; + unsigned int width; + unsigned int pitchOrLinearSize; + unsigned int depth; + unsigned int mipmapCount; + unsigned int reserved1[11]; + DDSPixelFormat ddspf; + unsigned int caps; + unsigned int caps2; + unsigned int caps3; + unsigned int caps4; + unsigned int reserved2; + } DDSHeader; + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + // Verify the type of file + unsigned char *ddsHeaderId = fileDataPtr; + fileDataPtr += 4; + + if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' ')) + { + TRACELOG(LOG_WARNING, "IMAGE: DDS file data not valid"); + } + else + { + DDSHeader *ddsHeader = (DDSHeader *)fileDataPtr; + + TRACELOGD("IMAGE: DDS file data info:"); + TRACELOGD(" > Header size: %i", sizeof(DDSHeader)); + TRACELOGD(" > Pixel format size: %i", ddsHeader->ddspf.size); + TRACELOGD(" > Pixel format flags: 0x%x", ddsHeader->ddspf.flags); + TRACELOGD(" > File format: 0x%x", ddsHeader->ddspf.fourCC); + TRACELOGD(" > File bit count: 0x%x", ddsHeader->ddspf.rgbBitCount); + + fileDataPtr += sizeof(DDSHeader); // Skip header + + image.width = ddsHeader->width; + image.height = ddsHeader->height; + + if (ddsHeader->mipmapCount == 0) image.mipmaps = 1; // Parameter not used + else image.mipmaps = ddsHeader->mipmapCount; + + if (ddsHeader->ddspf.rgbBitCount == 16) // 16bit mode, no compressed + { + if (ddsHeader->ddspf.flags == 0x40) // no alpha channel + { + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + } + else if (ddsHeader->ddspf.flags == 0x41) // with alpha channel + { + if (ddsHeader->ddspf.aBitMask == 0x8000) // 1bit alpha + { + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + unsigned char alpha = 0; + + // NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 15; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 1; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + } + else if (ddsHeader->ddspf.aBitMask == 0xf000) // 4bit alpha + { + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + unsigned char alpha = 0; + + // NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 12; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 4; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + } + } + } + else if (ddsHeader->ddspf.flags == 0x40 && ddsHeader->ddspf.rgbBitCount == 24) // DDS_RGB, no compressed + { + int dataSize = image.width*image.height*3*sizeof(unsigned char); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + } + else if (ddsHeader->ddspf.flags == 0x41 && ddsHeader->ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed + { + int dataSize = image.width*image.height*4*sizeof(unsigned char); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + unsigned char blue = 0; + + // NOTE: Data comes as A8R8G8B8, it must be reordered R8G8B8A8 (view next comment) + // DirecX understand ARGB as a 32bit DWORD but the actual memory byte alignment is BGRA + // So, we must realign B8G8R8A8 to R8G8B8A8 + for (int i = 0; i < image.width*image.height*4; i += 4) + { + blue = ((unsigned char *)image.data)[i]; + ((unsigned char *)image.data)[i] = ((unsigned char *)image.data)[i + 2]; + ((unsigned char *)image.data)[i + 2] = blue; + } + + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } + else if (((ddsHeader->ddspf.flags == 0x04) || (ddsHeader->ddspf.flags == 0x05)) && (ddsHeader->ddspf.fourCC > 0)) // Compressed + { + int dataSize = 0; + + // Calculate data size, including all mipmaps + if (ddsHeader->mipmapCount > 1) dataSize = ddsHeader->pitchOrLinearSize*2; + else dataSize = ddsHeader->pitchOrLinearSize; + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + switch (ddsHeader->ddspf.fourCC) + { + case FOURCC_DXT1: + { + if (ddsHeader->ddspf.flags == 0x04) image.format = PIXELFORMAT_COMPRESSED_DXT1_RGB; + else image.format = PIXELFORMAT_COMPRESSED_DXT1_RGBA; + } break; + case FOURCC_DXT3: image.format = PIXELFORMAT_COMPRESSED_DXT3_RGBA; break; + case FOURCC_DXT5: image.format = PIXELFORMAT_COMPRESSED_DXT5_RGBA; break; + default: break; + } + } + } + } + + return image; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_PKM) +// Loading PKM image data (ETC1/ETC2 compression) +// NOTE: KTX is the standard Khronos Group compression format (ETC1/ETC2, mipmaps) +// PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps) +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) + // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + // PKM file (ETC1) Header (16 bytes) + typedef struct { + char id[4]; // "PKM " + char version[2]; // "10" or "20" + unsigned short format; // Data format (big-endian) (Check list below) + unsigned short width; // Texture width (big-endian) (origWidth rounded to multiple of 4) + unsigned short height; // Texture height (big-endian) (origHeight rounded to multiple of 4) + unsigned short origWidth; // Original width (big-endian) + unsigned short origHeight; // Original height (big-endian) + } PKMHeader; + + // Formats list + // version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used) + // version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R + + // NOTE: The extended width and height are the widths rounded up to a multiple of 4. + // NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels) + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + PKMHeader *pkmHeader = (PKMHeader *)fileDataPtr; + + if ((pkmHeader->id[0] != 'P') || (pkmHeader->id[1] != 'K') || (pkmHeader->id[2] != 'M') || (pkmHeader->id[3] != ' ')) + { + TRACELOG(LOG_WARNING, "IMAGE: PKM file data not valid"); + } + else + { + fileDataPtr += sizeof(PKMHeader); // Skip header + + // NOTE: format, width and height come as big-endian, data must be swapped to little-endian + pkmHeader->format = ((pkmHeader->format & 0x00FF) << 8) | ((pkmHeader->format & 0xFF00) >> 8); + pkmHeader->width = ((pkmHeader->width & 0x00FF) << 8) | ((pkmHeader->width & 0xFF00) >> 8); + pkmHeader->height = ((pkmHeader->height & 0x00FF) << 8) | ((pkmHeader->height & 0xFF00) >> 8); + + TRACELOGD("IMAGE: PKM file data info:"); + TRACELOGD(" > Image width: %i", pkmHeader->width); + TRACELOGD(" > Image height: %i", pkmHeader->height); + TRACELOGD(" > Image format: %i", pkmHeader->format); + + image.width = pkmHeader->width; + image.height = pkmHeader->height; + image.mipmaps = 1; + + int bpp = 4; + if (pkmHeader->format == 3) bpp = 8; + + int dataSize = image.width*image.height*bpp/8; // Total data size in bytes + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + if (pkmHeader->format == 0) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (pkmHeader->format == 1) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (pkmHeader->format == 3) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; + } + } + + return image; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_KTX) +// Load KTX compressed image data (ETC1/ETC2 compression) +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) + // GL_ARB_ES3_compatibility (ETC2/EAC) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + // KTX file Header (64 bytes) + // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + // v2.0 - http://github.khronos.org/KTX-Specification/ + + // TODO: Support KTX 2.2 specs! + + typedef struct { + char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" + unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 + unsigned int glType; // For compressed textures, glType must equal 0 + unsigned int glTypeSize; // For compressed texture data, usually 1 + unsigned int glFormat; // For compressed textures is 0 + unsigned int glInternalFormat; // Compressed internal format + unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...) + unsigned int width; // Texture image width in pixels + unsigned int height; // Texture image height in pixels + unsigned int depth; // For 2D textures is 0 + unsigned int elements; // Number of array elements, usually 0 + unsigned int faces; // Cubemap faces, for no-cubemap = 1 + unsigned int mipmapLevels; // Non-mipmapped textures = 1 + unsigned int keyValueDataSize; // Used to encode any arbitrary data... + } KTXHeader; + + // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + KTXHeader *ktxHeader = (KTXHeader *)fileDataPtr; + + if ((ktxHeader->id[1] != 'K') || (ktxHeader->id[2] != 'T') || (ktxHeader->id[3] != 'X') || + (ktxHeader->id[4] != ' ') || (ktxHeader->id[5] != '1') || (ktxHeader->id[6] != '1')) + { + TRACELOG(LOG_WARNING, "IMAGE: KTX file data not valid"); + } + else + { + fileDataPtr += sizeof(KTXHeader); // Move file data pointer + + image.width = ktxHeader->width; + image.height = ktxHeader->height; + image.mipmaps = ktxHeader->mipmapLevels; + + TRACELOGD("IMAGE: KTX file data info:"); + TRACELOGD(" > Image width: %i", ktxHeader->width); + TRACELOGD(" > Image height: %i", ktxHeader->height); + TRACELOGD(" > Image format: 0x%x", ktxHeader->glInternalFormat); + + fileDataPtr += ktxHeader->keyValueDataSize; // Skip value data size + + int dataSize = ((int *)fileDataPtr)[0]; + fileDataPtr += sizeof(int); + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + if (ktxHeader->glInternalFormat == 0x8D64) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (ktxHeader->glInternalFormat == 0x9274) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (ktxHeader->glInternalFormat == 0x9278) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; + } + } + + return image; +} + +// Save image data as KTX file +// NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018) +static int SaveKTX(Image image, const char *fileName) +{ + // KTX file Header (64 bytes) + // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + // v2.0 - http://github.khronos.org/KTX-Specification/ - still on draft, not ready for implementation + typedef struct { + char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" // KTX 2.0: "«KTX 22»\r\n\x1A\n" + unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 + unsigned int glType; // For compressed textures, glType must equal 0 + unsigned int glTypeSize; // For compressed texture data, usually 1 + unsigned int glFormat; // For compressed textures is 0 + unsigned int glInternalFormat; // Compressed internal format + unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...) // KTX 2.0: UInt32 vkFormat + unsigned int width; // Texture image width in pixels + unsigned int height; // Texture image height in pixels + unsigned int depth; // For 2D textures is 0 + unsigned int elements; // Number of array elements, usually 0 + unsigned int faces; // Cubemap faces, for no-cubemap = 1 + unsigned int mipmapLevels; // Non-mipmapped textures = 1 + unsigned int keyValueDataSize; // Used to encode any arbitrary data... // KTX 2.0: UInt32 levelOrder - ordering of the mipmap levels, usually 0 + // KTX 2.0: UInt32 supercompressionScheme - 0 (None), 1 (Crunch CRN), 2 (Zlib DEFLATE)... + // KTX 2.0 defines additional header elements... + } KTXHeader; + + // Calculate file dataSize required + int dataSize = sizeof(KTXHeader); + + for (int i = 0, width = image.width, height = image.height; i < image.mipmaps; i++) + { + dataSize += GetPixelDataSize(width, height, image.format); + width /= 2; height /= 2; + } + + unsigned char *fileData = RL_CALLOC(dataSize, 1); + unsigned char *fileDataPtr = fileData; + + KTXHeader ktxHeader = { 0 }; + + // KTX identifier (v1.1) + //unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' }; + //unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + + const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' }; + + // Get the image header + memcpy(ktxHeader.id, ktxIdentifier, 12); // KTX 1.1 signature + ktxHeader.endianness = 0; + ktxHeader.glType = 0; // Obtained from image.format + ktxHeader.glTypeSize = 1; + ktxHeader.glFormat = 0; // Obtained from image.format + ktxHeader.glInternalFormat = 0; // Obtained from image.format + ktxHeader.glBaseInternalFormat = 0; + ktxHeader.width = image.width; + ktxHeader.height = image.height; + ktxHeader.depth = 0; + ktxHeader.elements = 0; + ktxHeader.faces = 1; + ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats) + ktxHeader.keyValueDataSize = 0; // No extra data after the header + + rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function + ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only + + // NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC + + if (ktxHeader.glFormat == -1) TRACELOG(LOG_WARNING, "IMAGE: GL format not supported for KTX export (%i)", ktxHeader.glFormat); + else + { + memcpy(fileDataPtr, &ktxHeader, sizeof(KTXHeader)); + fileDataPtr += sizeof(KTXHeader); + + int width = image.width; + int height = image.height; + int dataOffset = 0; + + // Save all mipmaps data + for (int i = 0; i < image.mipmaps; i++) + { + unsigned int dataSize = GetPixelDataSize(width, height, image.format); + + memcpy(fileDataPtr, &dataSize, sizeof(unsigned int)); + memcpy(fileDataPtr + 4, (unsigned char *)image.data + dataOffset, dataSize); + + width /= 2; + height /= 2; + dataOffset += dataSize; + fileDataPtr += (4 + dataSize); + } + } + + int success = SaveFileData(fileName, fileData, dataSize); + + RL_FREE(fileData); // Free file data buffer + + // If all data has been written correctly to file, success = 1 + return success; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_PVR) +// Loading PVR image data (uncompressed or PVRT compression) +// NOTE: PVR v2 not supported, use PVR v3 instead +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extension: + // GL_IMG_texture_compression_pvrtc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 + // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 + +#if 0 // Not used... + // PVR file v2 Header (52 bytes) + typedef struct { + unsigned int headerLength; + unsigned int height; + unsigned int width; + unsigned int numMipmaps; + unsigned int flags; + unsigned int dataLength; + unsigned int bpp; + unsigned int bitmaskRed; + unsigned int bitmaskGreen; + unsigned int bitmaskBlue; + unsigned int bitmaskAlpha; + unsigned int pvrTag; + unsigned int numSurfs; + } PVRHeaderV2; +#endif + + // PVR file v3 Header (52 bytes) + // NOTE: After it could be metadata (15 bytes?) + typedef struct { + char id[4]; + unsigned int flags; + unsigned char channels[4]; // pixelFormat high part + unsigned char channelDepth[4]; // pixelFormat low part + unsigned int colourSpace; + unsigned int channelType; + unsigned int height; + unsigned int width; + unsigned int depth; + unsigned int numSurfaces; + unsigned int numFaces; + unsigned int numMipmaps; + unsigned int metaDataSize; + } PVRHeaderV3; + +#if 0 // Not used... + // Metadata (usually 15 bytes) + typedef struct { + unsigned int devFOURCC; + unsigned int key; + unsigned int dataSize; // Not used? + unsigned char *data; // Not used? + } PVRMetadata; +#endif + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + // Check PVR image version + unsigned char pvrVersion = fileDataPtr[0]; + + // Load different PVR data formats + if (pvrVersion == 0x50) + { + PVRHeaderV3 *pvrHeader = (PVRHeaderV3 *)fileDataPtr; + + if ((pvrHeader->id[0] != 'P') || (pvrHeader->id[1] != 'V') || (pvrHeader->id[2] != 'R') || (pvrHeader->id[3] != 3)) + { + TRACELOG(LOG_WARNING, "IMAGE: PVR file data not valid"); + } + else + { + fileDataPtr += sizeof(PVRHeaderV3); // Skip header + + image.width = pvrHeader->width; + image.height = pvrHeader->height; + image.mipmaps = pvrHeader->numMipmaps; + + // Check data format + if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 0)) && (pvrHeader->channelDepth[0] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 'a')) && ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8))) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + else if ((pvrHeader->channels[0] == 'r') && (pvrHeader->channels[1] == 'g') && (pvrHeader->channels[2] == 'b')) + { + if (pvrHeader->channels[3] == 'a') + { + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 5) && (pvrHeader->channelDepth[2] == 5) && (pvrHeader->channelDepth[3] == 1)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((pvrHeader->channelDepth[0] == 4) && (pvrHeader->channelDepth[1] == 4) && (pvrHeader->channelDepth[2] == 4) && (pvrHeader->channelDepth[3] == 4)) image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8) && (pvrHeader->channelDepth[3] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } + else if (pvrHeader->channels[3] == 0) + { + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 6) && (pvrHeader->channelDepth[2] == 5)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + } + } + else if (pvrHeader->channels[0] == 2) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGB; + else if (pvrHeader->channels[0] == 3) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGBA; + + fileDataPtr += pvrHeader->metaDataSize; // Skip meta data header + + // Calculate data size (depends on format) + int bpp = 0; + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + default: break; + } + + int dataSize = image.width*image.height*bpp/8; // Total data size in bytes + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + } + } + else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: PVRv2 format not supported, update your files to PVRv3"); + } + + return image; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_ASTC) +// Load ASTC compressed image data (ASTC compression) +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extensions: + // GL_KHR_texture_compression_astc_hdr + // GL_KHR_texture_compression_astc_ldr + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 + // GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 + + // ASTC file Header (16 bytes) + typedef struct { + unsigned char id[4]; // Signature: 0x13 0xAB 0xA1 0x5C + unsigned char blockX; // Block X dimensions + unsigned char blockY; // Block Y dimensions + unsigned char blockZ; // Block Z dimensions (1 for 2D images) + unsigned char width[3]; // Image width in pixels (24bit value) + unsigned char height[3]; // Image height in pixels (24bit value) + unsigned char length[3]; // Image Z-size (1 for 2D images) + } ASTCHeader; + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + ASTCHeader *astcHeader = (ASTCHeader *)fileDataPtr; + + if ((astcHeader->id[3] != 0x5c) || (astcHeader->id[2] != 0xa1) || (astcHeader->id[1] != 0xab) || (astcHeader->id[0] != 0x13)) + { + TRACELOG(LOG_WARNING, "IMAGE: ASTC file data not valid"); + } + else + { + fileDataPtr += sizeof(ASTCHeader); // Skip header + + // NOTE: Assuming Little Endian (could it be wrong?) + image.width = 0x00000000 | ((int)astcHeader->width[2] << 16) | ((int)astcHeader->width[1] << 8) | ((int)astcHeader->width[0]); + image.height = 0x00000000 | ((int)astcHeader->height[2] << 16) | ((int)astcHeader->height[1] << 8) | ((int)astcHeader->height[0]); + + TRACELOGD("IMAGE: ASTC file data info:"); + TRACELOGD(" > Image width: %i", image.width); + TRACELOGD(" > Image height: %i", image.height); + TRACELOGD(" > Image blocks: %ix%i", astcHeader->blockX, astcHeader->blockY); + + image.mipmaps = 1; // NOTE: ASTC format only contains one mipmap level + + // NOTE: Each block is always stored in 128bit so we can calculate the bpp + int bpp = 128/(astcHeader->blockX*astcHeader->blockY); + + // NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8 + if ((bpp == 8) || (bpp == 2)) + { + int dataSize = image.width*image.height*bpp/8; // Data size in bytes + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + if (bpp == 8) image.format = PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA; + else if (bpp == 2) image.format = PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA; + } + else TRACELOG(LOG_WARNING, "IMAGE: ASTC block size configuration not supported"); + } + } + + return image; +} +#endif diff --git a/raylib/utils.c b/raylib/utils.c new file mode 100644 index 0000000..66804fb --- /dev/null +++ b/raylib/utils.c @@ -0,0 +1,436 @@ +/********************************************************************************************** +* +* raylib.utils - Some common utility functions +* +* CONFIGURATION: +* +* #define SUPPORT_TRACELOG +* Show TraceLog() output messages +* NOTE: By default LOG_DEBUG traces not shown +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // WARNING: Required for: LogType enum + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include "utils.h" + +#if defined(PLATFORM_ANDROID) + #include // Required for: Android error types + #include // Required for: Android log system: __android_log_vprint() + #include // Required for: Android assets manager: AAsset, AAssetManager_open(), ... +#endif + +#include // Required for: exit() +#include // Required for: FILE, fopen(), fseek(), ftell(), fread(), fwrite(), fprintf(), vprintf(), fclose() +#include // Required for: va_list, va_start(), va_end() +#include // Required for: strcpy(), strcat() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_TRACELOG_MSG_LENGTH + #define MAX_TRACELOG_MSG_LENGTH 128 // Max length of one trace-log message +#endif +#ifndef MAX_UWP_MESSAGES + #define MAX_UWP_MESSAGES 512 // Max UWP messages to process +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static int logTypeLevel = LOG_INFO; // Minimum log type level + +static TraceLogCallback traceLog = NULL; // TraceLog callback function pointer +static LoadFileDataCallback loadFileData = NULL; // LoadFileData callback funtion pointer +static SaveFileDataCallback saveFileData = NULL; // SaveFileText callback funtion pointer +static LoadFileTextCallback loadFileText = NULL; // LoadFileText callback funtion pointer +static SaveFileTextCallback saveFileText = NULL; // SaveFileText callback funtion pointer + +//---------------------------------------------------------------------------------- +// Functions to set internal callbacks +//---------------------------------------------------------------------------------- +void SetTraceLogCallback(TraceLogCallback callback) { traceLog = callback; } // Set custom trace log +void SetLoadFileDataCallback(LoadFileDataCallback callback) { loadFileData = callback; } // Set custom file data loader +void SetSaveFileDataCallback(SaveFileDataCallback callback) { saveFileData = callback; } // Set custom file data saver +void SetLoadFileTextCallback(LoadFileTextCallback callback) { loadFileText = callback; } // Set custom file text loader +void SetSaveFileTextCallback(SaveFileTextCallback callback) { saveFileText = callback; } // Set custom file text saver + + +#if defined(PLATFORM_ANDROID) +static AAssetManager *assetManager = NULL; // Android assets manager pointer +static const char *internalDataPath = NULL; // Android internal data path +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) +FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); + +static int android_read(void *cookie, char *buf, int size); +static int android_write(void *cookie, const char *buf, int size); +static fpos_t android_seek(void *cookie, fpos_t offset, int whence); +static int android_close(void *cookie); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utilities +//---------------------------------------------------------------------------------- + +// Set the current threshold (minimum) log level +void SetTraceLogLevel(int logType) { logTypeLevel = logType; } + +// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +void TraceLog(int logType, const char *text, ...) +{ +#if defined(SUPPORT_TRACELOG) + // Message has level below current threshold, don't emit + if (logType < logTypeLevel) return; + + va_list args; + va_start(args, text); + + if (traceLog) + { + traceLog(logType, text, args); + va_end(args); + return; + } + +#if defined(PLATFORM_ANDROID) + switch(logType) + { + case LOG_TRACE: __android_log_vprint(ANDROID_LOG_VERBOSE, "raylib", text, args); break; + case LOG_DEBUG: __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", text, args); break; + case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib", text, args); break; + case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib", text, args); break; + case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib", text, args); break; + case LOG_FATAL: __android_log_vprint(ANDROID_LOG_FATAL, "raylib", text, args); break; + default: break; + } +#else + char buffer[MAX_TRACELOG_MSG_LENGTH] = { 0 }; + + switch (logType) + { + case LOG_TRACE: strcpy(buffer, "TRACE: "); break; + case LOG_DEBUG: strcpy(buffer, "DEBUG: "); break; + case LOG_INFO: strcpy(buffer, "INFO: "); break; + case LOG_WARNING: strcpy(buffer, "WARNING: "); break; + case LOG_ERROR: strcpy(buffer, "ERROR: "); break; + case LOG_FATAL: strcpy(buffer, "FATAL: "); break; + default: break; + } + + strcat(buffer, text); + strcat(buffer, "\n"); + vprintf(buffer, args); +#endif + + va_end(args); + + if (logType == LOG_ERROR) exit(1); // If error, exit program + +#endif // SUPPORT_TRACELOG +} + +// Internal memory allocator +// NOTE: Initializes to zero by default +void *MemAlloc(int size) +{ + void *ptr = RL_CALLOC(size, 1); + return ptr; +} + +// Internal memory reallocator +void *MemRealloc(void *ptr, int size) +{ + void *ret = RL_REALLOC(ptr, size); + return ret; +} + +// Internal memory free +void MemFree(void *ptr) +{ + RL_FREE(ptr); +} + +// Load data from file into a buffer +unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) +{ + unsigned char *data = NULL; + *bytesRead = 0; + + if (fileName != NULL) + { + if (loadFileData) + { + data = loadFileData(fileName, bytesRead); + return data; + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); + + // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] + unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file); + *bytesRead = count; + + if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Unload file data allocated by LoadFileData() +void UnloadFileData(unsigned char *data) +{ + RL_FREE(data); +} + +// Save data to file from buffer +bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) +{ + bool success = false; + + if (fileName != NULL) + { + if (saveFileData) + { + return saveFileData(fileName, data, bytesToWrite); + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), bytesToWrite, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != bytesToWrite) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + int result = fclose(file); + if (result == 0) success = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; +} + +// Load text data from file, returns a '\0' terminated string +// NOTE: text chars array should be freed manually +char *LoadFileText(const char *fileName) +{ + char *text = NULL; + + if (fileName != NULL) + { + if (loadFileText) + { + text = loadFileText(fileName); + return text; + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "rt"); + + if (file != NULL) + { + // WARNING: When reading a file as 'text' file, + // text mode causes carriage return-linefeed translation... + // ...but using fseek() should return correct byte-offset + fseek(file, 0, SEEK_END); + unsigned int size = (unsigned int)ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + text = (char *)RL_MALLOC((size + 1)*sizeof(char)); + unsigned int count = (unsigned int)fread(text, sizeof(char), size, file); + + // WARNING: \r\n is converted to \n on reading, so, + // read bytes count gets reduced by the number of lines + if (count < size) text = RL_REALLOC(text, count + 1); + + // Zero-terminate the string + text[count] = '\0'; + + TRACELOG(LOG_INFO, "FILEIO: [%s] Text file loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return text; +} + +// Unload file text data allocated by LoadFileText() +void UnloadFileText(unsigned char *text) +{ + RL_FREE(text); +} + +// Save text data to file (write), string must be '\0' terminated +bool SaveFileText(const char *fileName, char *text) +{ + bool success = false; + + if (fileName != NULL) + { + if (saveFileText) + { + return saveFileText(fileName, text); + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = fprintf(file, "%s", text); + + if (count < 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + int result = fclose(file); + if (result == 0) success = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; +} + +#if defined(PLATFORM_ANDROID) +// Initialize asset manager from android app +void InitAssetManager(AAssetManager *manager, const char *dataPath) +{ + assetManager = manager; + internalDataPath = dataPath; +} + +// Replacement for fopen() +// Ref: https://developer.android.com/ndk/reference/group/asset +FILE *android_fopen(const char *fileName, const char *mode) +{ + if (mode[0] == 'w') + { + // TODO: fopen() is mapped to android_fopen() that only grants read access + // to assets directory through AAssetManager but we want to also be able to + // write data when required using the standard stdio FILE access functions + // Ref: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only + #undef fopen + return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); + #define fopen(name, mode) android_fopen(name, mode) + } + else + { + // NOTE: AAsset provides access to read-only asset + AAsset *asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_UNKNOWN); + + if (asset != NULL) + { + // Return pointer to file in the assets + return funopen(asset, android_read, android_write, android_seek, android_close); + } + else + { + #undef fopen + // Just do a regular open if file is not found in the assets + return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); + #define fopen(name, mode) android_fopen(name, mode) + } + } +} +#endif // PLATFORM_ANDROID + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) +static int android_read(void *cookie, char *buf, int size) +{ + return AAsset_read((AAsset *)cookie, buf, size); +} + +static int android_write(void *cookie, const char *buf, int size) +{ + TRACELOG(LOG_WARNING, "ANDROID: Failed to provide write access to APK"); + + return EACCES; +} + +static fpos_t android_seek(void *cookie, fpos_t offset, int whence) +{ + return AAsset_seek((AAsset *)cookie, offset, whence); +} + +static int android_close(void *cookie) +{ + AAsset_close((AAsset *)cookie); + return 0; +} +#endif // PLATFORM_ANDROID diff --git a/raylib/utils.h b/raylib/utils.h new file mode 100644 index 0000000..3d7a379 --- /dev/null +++ b/raylib/utils.h @@ -0,0 +1,79 @@ +/********************************************************************************************** +* +* raylib.utils - Some common utility functions +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef UTILS_H +#define UTILS_H + +#if defined(PLATFORM_ANDROID) + #include // Required for: FILE + #include // Required for: AAssetManager +#endif + +#if defined(SUPPORT_TRACELOG) + #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) + + #if defined(SUPPORT_TRACELOG_DEBUG) + #define TRACELOGD(...) TraceLog(LOG_DEBUG, __VA_ARGS__) + #else + #define TRACELOGD(...) (void)0 + #endif +#else + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) + #define fopen(name, mode) android_fopen(name, mode) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// Nop... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) +void InitAssetManager(AAssetManager *manager, const char *dataPath); // Initialize asset manager from android app +FILE *android_fopen(const char *fileName, const char *mode); // Replacement for fopen() -> Read-only! +#endif + +#ifdef __cplusplus +} +#endif + +#endif // UTILS_H diff --git a/raylib/uwp_events.h b/raylib/uwp_events.h new file mode 100644 index 0000000..2c403fd --- /dev/null +++ b/raylib/uwp_events.h @@ -0,0 +1,119 @@ +/********************************************************************************************** +* +* raylib.uwp_events - Functions for bootstrapping UWP functionality within raylib's core. +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2020-2020 Reece Mackie (@Rover656) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef UWP_EVENTS_H +#define UWP_EVENTS_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(PLATFORM_UWP) + +// Determine if UWP functions are set and ready for raylib's use. +bool UWPIsConfigured(); + +// Call this to set the UWP data path you wish for saving and loading. +void UWPSetDataPath(const char* path); + +// Function for getting program time. +typedef double(*UWPQueryTimeFunc)(); +UWPQueryTimeFunc UWPGetQueryTimeFunc(void); +void UWPSetQueryTimeFunc(UWPQueryTimeFunc func); + +// Function for sleeping the current thread +typedef void (*UWPSleepFunc)(double sleepUntil); +UWPSleepFunc UWPGetSleepFunc(void); +void UWPSetSleepFunc(UWPSleepFunc func); + +// Function for querying the display size +typedef void(*UWPDisplaySizeFunc)(int* width, int* height); +UWPDisplaySizeFunc UWPGetDisplaySizeFunc(void); +void UWPSetDisplaySizeFunc(UWPDisplaySizeFunc func); + +// Functions for mouse cursor control +typedef void(*UWPMouseFunc)(void); +UWPMouseFunc UWPGetMouseLockFunc(); +void UWPSetMouseLockFunc(UWPMouseFunc func); +UWPMouseFunc UWPGetMouseUnlockFunc(); +void UWPSetMouseUnlockFunc(UWPMouseFunc func); +UWPMouseFunc UWPGetMouseShowFunc(); +void UWPSetMouseShowFunc(UWPMouseFunc func); +UWPMouseFunc UWPGetMouseHideFunc(); +void UWPSetMouseHideFunc(UWPMouseFunc func); + +// Function for setting mouse cursor position. +typedef void (*UWPMouseSetPosFunc)(int x, int y); +UWPMouseSetPosFunc UWPGetMouseSetPosFunc(); +void UWPSetMouseSetPosFunc(UWPMouseSetPosFunc func); + +// The below functions are implemented in core.c but are placed here so they can be called by user code. +// This choice is made as platform-specific code is preferred to be kept away from raylib.h + +// Call this when a Key is pressed or released. +void UWPKeyDownEvent(int key, bool down, bool controlKey); + +// Call this on the CoreWindow::CharacterRecieved event +void UWPKeyCharEvent(int key); + +// Call when a mouse button state changes +void UWPMouseButtonEvent(int button, bool down); + +// Call when the mouse cursor moves +void UWPMousePosEvent(double x, double y); + +// Call when the mouse wheel moves +void UWPMouseWheelEvent(int deltaY); + +// Call when the window resizes +void UWPResizeEvent(int width, int height); + +// Call when a gamepad is made active +void UWPActivateGamepadEvent(int gamepad, bool active); + +// Call when a gamepad button state changes +void UWPRegisterGamepadButton(int gamepad, int button, bool down); + +// Call when a gamepad axis state changes +void UWPRegisterGamepadAxis(int gamepad, int axis, float value); + +// Call when the touch point moves +void UWPGestureMove(int pointer, float x, float y); + +// Call when there is a touch down or up +void UWPGestureTouch(int pointer, float x, float y, bool touch); + +// Set the core window pointer so that we can pass it to EGL. +void* UWPGetCoreWindowPtr(); +void UWPSetCoreWindowPtr(void* ptr); + +#if defined(__cplusplus) +} +#endif + +#endif // PLATFORM_UWP + +#endif // UWP_EVENTS_H diff --git a/raylib_pi4_test/camera.h b/raylib_pi4_test/camera.h new file mode 100644 index 0000000..cd42b54 --- /dev/null +++ b/raylib_pi4_test/camera.h @@ -0,0 +1,522 @@ +/******************************************************************************************* +* +* raylib.camera - Camera system with multiple modes support +* +* NOTE: Memory footprint of this library is aproximately 52 bytes (global variables) +* +* CONFIGURATION: +* +* #define CAMERA_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define CAMERA_STANDALONE +* If defined, the library can be used as standalone as a camera system but some +* functions must be redefined to manage inputs accordingly. +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, update and maintenance +* Marc Palau: Initial implementation (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef CAMERA_H +#define CAMERA_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for CAMERA_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(CAMERA_STANDALONE) + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Camera type, defines a camera position/orientation in 3d space + typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view apperture in Y (degrees) in perspective, used as near plane width in orthographic + int type; // Camera type, defines projection type: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC + } Camera3D; + + typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + + // Camera system modes + typedef enum { + CAMERA_CUSTOM = 0, + CAMERA_FREE, + CAMERA_ORBITAL, + CAMERA_FIRST_PERSON, + CAMERA_THIRD_PERSON + } CameraMode; + + // Camera projection modes + typedef enum { + CAMERA_PERSPECTIVE = 0, + CAMERA_ORTHOGRAPHIC + } CameraProjection; +#endif + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(CAMERA_STANDALONE) +void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) +void UpdateCamera(Camera *camera); // Update camera position for selected mode + +void SetCameraPanControl(int keyPan); // Set camera pan key to combine with mouse movement (free camera) +void SetCameraAltControl(int keyAlt); // Set camera alt key to combine with mouse movement (free camera) +void SetCameraSmoothZoomControl(int szoomKey); // Set camera smooth zoom key to combine with mouse (free camera) +void SetCameraMoveControls(int keyFront, int keyBack, + int keyRight, int keyLeft, + int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) +#endif + +#ifdef __cplusplus +} +#endif + +#endif // CAMERA_H + + +/*********************************************************************************** +* +* CAMERA IMPLEMENTATION +* +************************************************************************************/ + +#if defined(CAMERA_IMPLEMENTATION) + +#include // Required for: sinf(), cosf(), sqrtf() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846 +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Camera mouse movement sensitivity +#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f +#define CAMERA_MOUSE_SCROLL_SENSITIVITY 1.5f + +// FREE_CAMERA +#define CAMERA_FREE_MOUSE_SENSITIVITY 0.01f +#define CAMERA_FREE_DISTANCE_MIN_CLAMP 0.3f +#define CAMERA_FREE_DISTANCE_MAX_CLAMP 120.0f +#define CAMERA_FREE_MIN_CLAMP 85.0f +#define CAMERA_FREE_MAX_CLAMP -85.0f +#define CAMERA_FREE_SMOOTH_ZOOM_SENSITIVITY 0.05f +#define CAMERA_FREE_PANNING_DIVIDER 5.1f + +// ORBITAL_CAMERA +#define CAMERA_ORBITAL_SPEED 0.01f // Radians per frame + +// FIRST_PERSON +//#define CAMERA_FIRST_PERSON_MOUSE_SENSITIVITY 0.003f +#define CAMERA_FIRST_PERSON_FOCUS_DISTANCE 25.0f +#define CAMERA_FIRST_PERSON_MIN_CLAMP 89.0f +#define CAMERA_FIRST_PERSON_MAX_CLAMP -89.0f + +#define CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER 8.0f +#define CAMERA_FIRST_PERSON_STEP_DIVIDER 30.0f +#define CAMERA_FIRST_PERSON_WAVING_DIVIDER 200.0f + +// THIRD_PERSON +//#define CAMERA_THIRD_PERSON_MOUSE_SENSITIVITY 0.003f +#define CAMERA_THIRD_PERSON_DISTANCE_CLAMP 1.2f +#define CAMERA_THIRD_PERSON_MIN_CLAMP 5.0f +#define CAMERA_THIRD_PERSON_MAX_CLAMP -85.0f +#define CAMERA_THIRD_PERSON_OFFSET (Vector3){ 0.4f, 0.0f, 0.0f } + +// PLAYER (used by camera) +#define PLAYER_MOVEMENT_SENSITIVITY 20.0f + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Camera move modes (first person and third person cameras) +typedef enum { + MOVE_FRONT = 0, + MOVE_BACK, + MOVE_RIGHT, + MOVE_LEFT, + MOVE_UP, + MOVE_DOWN +} CameraMove; + +// Camera global state context data [56 bytes] +typedef struct { + unsigned int mode; // Current camera mode + float targetDistance; // Camera distance from position to target + float playerEyesPosition; // Player eyes position from ground (in meters) + Vector2 angle; // Camera angle in plane XZ + + // Camera movement control keys + int moveControl[6]; // Move controls (CAMERA_FIRST_PERSON) + int smoothZoomControl; // Smooth zoom control key + int altControl; // Alternative control key + int panControl; // Pan view control key +} CameraData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static CameraData CAMERA = { // Global CAMERA state context + .mode = 0, + .targetDistance = 0, + .playerEyesPosition = 1.85f, + .angle = { 0 }, + .moveControl = { 'W', 'S', 'D', 'A', 'E', 'Q' }, + .smoothZoomControl = 341, // raylib: KEY_LEFT_CONTROL + .altControl = 342, // raylib: KEY_LEFT_ALT + .panControl = 2 // raylib: MOUSE_MIDDLE_BUTTON +}; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(CAMERA_STANDALONE) +// NOTE: Camera controls depend on some raylib input functions +static void EnableCursor() {} // Unlock cursor +static void DisableCursor() {} // Lock cursor + +static int IsKeyDown(int key) { return 0; } + +static int IsMouseButtonDown(int button) { return 0;} +static float GetMouseWheelMove() { return 0.0f; } +static Vector2 GetMousePosition() { return (Vector2){ 0.0f, 0.0f }; } +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Select camera mode (multiple camera modes available) +void SetCameraMode(Camera camera, int mode) +{ + Vector3 v1 = camera.position; + Vector3 v2 = camera.target; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + + CAMERA.targetDistance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance to target + + // Camera angle calculation + CAMERA.angle.x = atan2f(dx, dz); // Camera angle in plane XZ (0 aligned with Z, move positive CCW) + CAMERA.angle.y = atan2f(dy, sqrtf(dx*dx + dz*dz)); // Camera angle in plane XY (0 aligned with X, move positive CW) + + CAMERA.playerEyesPosition = camera.position.y; // Init player eyes position to camera Y position + + // Lock cursor for first person and third person cameras + if ((mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON)) DisableCursor(); + else EnableCursor(); + + CAMERA.mode = mode; +} + +// Update camera depending on selected mode +// NOTE: Camera controls depend on some raylib functions: +// System: EnableCursor(), DisableCursor() +// Mouse: IsMouseButtonDown(), GetMousePosition(), GetMouseWheelMove() +// Keys: IsKeyDown() +// TODO: Port to quaternion-based camera (?) +void UpdateCamera(Camera *camera) +{ + static int swingCounter = 0; // Used for 1st person swinging movement + static Vector2 previousMousePosition = { 0.0f, 0.0f }; + + // TODO: Compute CAMERA.targetDistance and CAMERA.angle here (?) + + // Mouse movement detection + Vector2 mousePositionDelta = { 0.0f, 0.0f }; + Vector2 mousePosition = GetMousePosition(); + float mouseWheelMove = GetMouseWheelMove(); + + // Keys input detection + // TODO: Input detection is raylib-dependant, it could be moved outside the module + bool keyPan = IsMouseButtonDown(CAMERA.panControl); + bool keyAlt = IsKeyDown(CAMERA.altControl); + bool szoomKey = IsKeyDown(CAMERA.smoothZoomControl); + bool direction[6] = { IsKeyDown(CAMERA.moveControl[MOVE_FRONT]), + IsKeyDown(CAMERA.moveControl[MOVE_BACK]), + IsKeyDown(CAMERA.moveControl[MOVE_RIGHT]), + IsKeyDown(CAMERA.moveControl[MOVE_LEFT]), + IsKeyDown(CAMERA.moveControl[MOVE_UP]), + IsKeyDown(CAMERA.moveControl[MOVE_DOWN]) }; + + if (CAMERA.mode != CAMERA_CUSTOM) + { + mousePositionDelta.x = mousePosition.x - previousMousePosition.x; + mousePositionDelta.y = mousePosition.y - previousMousePosition.y; + + previousMousePosition = mousePosition; + } + + // Support for multiple automatic camera modes + // NOTE: In case of CAMERA_CUSTOM nothing happens here, user must update it manually + switch (CAMERA.mode) + { + case CAMERA_FREE: // Camera free controls, using standard 3d-content-creation scheme + { + // Camera zoom + if ((CAMERA.targetDistance < CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) + { + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (CAMERA.targetDistance > CAMERA_FREE_DISTANCE_MAX_CLAMP) CAMERA.targetDistance = CAMERA_FREE_DISTANCE_MAX_CLAMP; + } + + // Camera looking down + else if ((camera->position.y > camera->target.y) && (CAMERA.targetDistance == CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y >= 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + + // if (camera->target.y < 0) camera->target.y = -0.001; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y < 0) && (mouseWheelMove > 0)) + { + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (CAMERA.targetDistance < CAMERA_FREE_DISTANCE_MIN_CLAMP) CAMERA.targetDistance = CAMERA_FREE_DISTANCE_MIN_CLAMP; + } + // Camera looking up + else if ((camera->position.y < camera->target.y) && (CAMERA.targetDistance == CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y <= 0)) + { + camera->target.x += mouseWheelMove*(camera->target.x - camera->position.x)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.y += mouseWheelMove*(camera->target.y - camera->position.y)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + camera->target.z += mouseWheelMove*(camera->target.z - camera->position.z)*CAMERA_MOUSE_SCROLL_SENSITIVITY/CAMERA.targetDistance; + + // if (camera->target.y > 0) camera->target.y = 0.001; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y > 0) && (mouseWheelMove > 0)) + { + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (CAMERA.targetDistance < CAMERA_FREE_DISTANCE_MIN_CLAMP) CAMERA.targetDistance = CAMERA_FREE_DISTANCE_MIN_CLAMP; + } + + // Input keys checks + if (keyPan) + { + if (keyAlt) // Alternative key behaviour + { + if (szoomKey) + { + // Camera smooth zoom + CAMERA.targetDistance += (mousePositionDelta.y*CAMERA_FREE_SMOOTH_ZOOM_SENSITIVITY); + } + else + { + // Camera rotation + CAMERA.angle.x += mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY; + CAMERA.angle.y += mousePositionDelta.y*-CAMERA_FREE_MOUSE_SENSITIVITY; + + // Angle clamp + if (CAMERA.angle.y > CAMERA_FREE_MIN_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FREE_MIN_CLAMP*DEG2RAD; + else if (CAMERA.angle.y < CAMERA_FREE_MAX_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FREE_MAX_CLAMP*DEG2RAD; + } + } + else + { + // Camera panning + camera->target.x += ((mousePositionDelta.x*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.y += ((mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.z += ((mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + } + } + + // Update camera position with changes + camera->position.x = -sinf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.x; + camera->position.y = -sinf(CAMERA.angle.y)*CAMERA.targetDistance + camera->target.y; + camera->position.z = -cosf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.z; + + } break; + case CAMERA_ORBITAL: // Camera just orbits around target, only zoom allowed + { + CAMERA.angle.x += CAMERA_ORBITAL_SPEED; // Camera orbit angle + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); // Camera zoom + + // Camera distance clamp + if (CAMERA.targetDistance < CAMERA_THIRD_PERSON_DISTANCE_CLAMP) CAMERA.targetDistance = CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + + // Update camera position with changes + camera->position.x = sinf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.x; + camera->position.y = ((CAMERA.angle.y <= 0.0f)? 1 : -1)*sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; + camera->position.z = cosf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.z; + + } break; + case CAMERA_FIRST_PERSON: // Camera moves as in a first-person game, controls are configurable + { + camera->position.x += (sinf(CAMERA.angle.x)*direction[MOVE_BACK] - + sinf(CAMERA.angle.x)*direction[MOVE_FRONT] - + cosf(CAMERA.angle.x)*direction[MOVE_LEFT] + + cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.y += (sinf(CAMERA.angle.y)*direction[MOVE_FRONT] - + sinf(CAMERA.angle.y)*direction[MOVE_BACK] + + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.z += (cosf(CAMERA.angle.x)*direction[MOVE_BACK] - + cosf(CAMERA.angle.x)*direction[MOVE_FRONT] + + sinf(CAMERA.angle.x)*direction[MOVE_LEFT] - + sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + // Camera orientation calculation + CAMERA.angle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); + CAMERA.angle.y += (mousePositionDelta.y*-CAMERA_MOUSE_MOVE_SENSITIVITY); + + // Angle clamp + if (CAMERA.angle.y > CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD; + else if (CAMERA.angle.y < CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FIRST_PERSON_MAX_CLAMP*DEG2RAD; + + // Recalculate camera target considering translation and rotation + Matrix translation = MatrixTranslate(0, 0, (CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER)); + Matrix rotation = MatrixRotateXYZ((Vector3){ PI*2 - CAMERA.angle.y, PI*2 - CAMERA.angle.x, 0 }); + Matrix transform = MatrixMultiply(translation, rotation); + + camera->target.x = camera->position.x - transform.m12; + camera->target.y = camera->position.y - transform.m13; + camera->target.z = camera->position.z - transform.m14; + + // If movement detected (some key pressed), increase swinging + for (int i = 0; i < 6; i++) if (direction[i]) { swingCounter++; break; } + + // Camera position update + // NOTE: On CAMERA_FIRST_PERSON player Y-movement is limited to player 'eyes position' + camera->position.y = CAMERA.playerEyesPosition - sinf(swingCounter/CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER)/CAMERA_FIRST_PERSON_STEP_DIVIDER; + + camera->up.x = sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; + camera->up.z = -sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; + + } break; + case CAMERA_THIRD_PERSON: // Camera moves as in a third-person game, following target at a distance, controls are configurable + { + camera->position.x += (sinf(CAMERA.angle.x)*direction[MOVE_BACK] - + sinf(CAMERA.angle.x)*direction[MOVE_FRONT] - + cosf(CAMERA.angle.x)*direction[MOVE_LEFT] + + cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.y += (sinf(CAMERA.angle.y)*direction[MOVE_FRONT] - + sinf(CAMERA.angle.y)*direction[MOVE_BACK] + + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.z += (cosf(CAMERA.angle.x)*direction[MOVE_BACK] - + cosf(CAMERA.angle.x)*direction[MOVE_FRONT] + + sinf(CAMERA.angle.x)*direction[MOVE_LEFT] - + sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + + // Camera orientation calculation + CAMERA.angle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); + CAMERA.angle.y += (mousePositionDelta.y*-CAMERA_MOUSE_MOVE_SENSITIVITY); + + // Angle clamp + if (CAMERA.angle.y > CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_THIRD_PERSON_MIN_CLAMP*DEG2RAD; + else if (CAMERA.angle.y < CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_THIRD_PERSON_MAX_CLAMP*DEG2RAD; + + // Camera zoom + CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); + + // Camera distance clamp + if (CAMERA.targetDistance < CAMERA_THIRD_PERSON_DISTANCE_CLAMP) CAMERA.targetDistance = CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + + // TODO: It seems camera->position is not correctly updated or some rounding issue makes the camera move straight to camera->target... + camera->position.x = sinf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.x; + + if (CAMERA.angle.y <= 0.0f) camera->position.y = sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; + else camera->position.y = -sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; + + camera->position.z = cosf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.z; + + } break; + case CAMERA_CUSTOM: break; + default: break; + } +} + +// Set camera pan key to combine with mouse movement (free camera) +void SetCameraPanControl(int keyPan) { CAMERA.panControl = keyPan; } + +// Set camera alt key to combine with mouse movement (free camera) +void SetCameraAltControl(int keyAlt) { CAMERA.altControl = keyAlt; } + +// Set camera smooth zoom key to combine with mouse (free camera) +void SetCameraSmoothZoomControl(int szoomKey) { CAMERA.smoothZoomControl = szoomKey; } + +// Set camera move controls (1st person and 3rd person cameras) +void SetCameraMoveControls(int keyFront, int keyBack, int keyRight, int keyLeft, int keyUp, int keyDown) +{ + CAMERA.moveControl[MOVE_FRONT] = keyFront; + CAMERA.moveControl[MOVE_BACK] = keyBack; + CAMERA.moveControl[MOVE_RIGHT] = keyRight; + CAMERA.moveControl[MOVE_LEFT] = keyLeft; + CAMERA.moveControl[MOVE_UP] = keyUp; + CAMERA.moveControl[MOVE_DOWN] = keyDown; +} + +#endif // CAMERA_IMPLEMENTATION diff --git a/raylib_pi4_test/config.h b/raylib_pi4_test/config.h new file mode 100644 index 0000000..99cb1d4 --- /dev/null +++ b/raylib_pi4_test/config.h @@ -0,0 +1,213 @@ +/********************************************************************************************** +* +* raylib configuration flags +* +* This file defines all the configuration flags for the different raylib modules +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2018-2021 Ahmad Fatoum & Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#define RAYLIB_VERSION "3.7" + +//------------------------------------------------------------------------------------ +// Module: core - Configuration Flags +//------------------------------------------------------------------------------------ +// Camera module is included (camera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital +#define SUPPORT_CAMERA_SYSTEM 1 +// Gestures module is included (gestures.h) to support gestures detection: tap, hold, swipe, drag +#define SUPPORT_GESTURES_SYSTEM 1 +// Mouse gestures are directly mapped like touches and processed by gestures system +#define SUPPORT_MOUSE_GESTURES 1 +// Reconfigure standard input to receive key inputs, works with SSH connection. +#define SUPPORT_SSH_KEYBOARD_RPI 1 +// Draw a mouse pointer on screen +#define SUPPORT_MOUSE_CURSOR_NATIVE 1 +// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. +// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. +#define SUPPORT_WINMM_HIGHRES_TIMER 1 +// Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used +//#define SUPPORT_BUSY_WAIT_LOOP 1 +// Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end +#define SUPPORT_HALFBUSY_WAIT_LOOP +// Wait for events passively (sleeping while no events) instead of polling them actively every frame +//#define SUPPORT_EVENTS_WAITING 1 +// Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() +#define SUPPORT_SCREEN_CAPTURE 1 +// Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() +#define SUPPORT_GIF_RECORDING 1 +// Support CompressData() and DecompressData() functions +#define SUPPORT_COMPRESSION_API 1 +// Support saving binary data automatically to a generated storage.data file. This file is managed internally. +#define SUPPORT_DATA_STORAGE 1 + +// core: Configuration values +//------------------------------------------------------------------------------------ +#if defined(__linux__) + #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) +#else + #define MAX_FILEPATH_LENGTH 512 // Maximum length supported for filepaths +#endif + +#define MAX_GAMEPADS 4 // Max number of gamepads supported +#define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) +#define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) +#define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported +#define MAX_KEY_PRESSED_QUEUE 16 // Max number of characters in the key input queue + +#define STORAGE_DATA_FILE "storage.data" // Automatic storage filename + +#define MAX_DECOMPRESSION_SIZE 64 // Max size allocated for decompression in MB + + +//------------------------------------------------------------------------------------ +// Module: rlgl - Configuration values +//------------------------------------------------------------------------------------ +// Show OpenGL extensions and capabilities detailed logs on init +//#define SUPPORT_GL_DETAILS_INFO 1 + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + #define DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch limits +#elif defined(GRAPHICS_API_OPENGL_ES2) + #define DEFAULT_BATCH_BUFFER_ELEMENTS 2048 // Default internal render batch limits +#endif + +#define DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#define DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) + +#define MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +#define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#define MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported + +#define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance +#define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance + +// Default shader vertex attribute names to set location points +#define DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Binded by default to shader location: 0 +#define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Binded by default to shader location: 1 +#define DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Binded by default to shader location: 2 +#define DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Binded by default to shader location: 3 +#define DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Binded by default to shader location: 4 +#define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Binded by default to shader location: 5 + + +//------------------------------------------------------------------------------------ +// Module: shapes - Configuration Flags +//------------------------------------------------------------------------------------ +// Use QUADS instead of TRIANGLES for drawing when possible +// Some lines-based shapes could still use lines +#define SUPPORT_QUADS_DRAW_MODE 1 + + +//------------------------------------------------------------------------------------ +// Module: textures - Configuration Flags +//------------------------------------------------------------------------------------ +// Selecte desired fileformats to be supported for image data loading +#define SUPPORT_FILEFORMAT_PNG 1 +//#define SUPPORT_FILEFORMAT_BMP 1 +//#define SUPPORT_FILEFORMAT_TGA 1 +//#define SUPPORT_FILEFORMAT_JPG 1 +#define SUPPORT_FILEFORMAT_GIF 1 +//#define SUPPORT_FILEFORMAT_PSD 1 +#define SUPPORT_FILEFORMAT_DDS 1 +#define SUPPORT_FILEFORMAT_HDR 1 +//#define SUPPORT_FILEFORMAT_KTX 1 +//#define SUPPORT_FILEFORMAT_ASTC 1 +//#define SUPPORT_FILEFORMAT_PKM 1 +//#define SUPPORT_FILEFORMAT_PVR 1 + +// Support image export functionality (.png, .bmp, .tga, .jpg) +#define SUPPORT_IMAGE_EXPORT 1 +// Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) +#define SUPPORT_IMAGE_GENERATION 1 +// Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... +// If not defined, still some functions are supported: ImageFormat(), ImageCrop(), ImageToPOT() +#define SUPPORT_IMAGE_MANIPULATION 1 + + +//------------------------------------------------------------------------------------ +// Module: text - Configuration Flags +//------------------------------------------------------------------------------------ +// Default font is loaded on window initialization to be available for the user to render simple text +// NOTE: If enabled, uses external module functions to load default raylib font +#define SUPPORT_DEFAULT_FONT 1 +// Selected desired font fileformats to be supported for loading +#define SUPPORT_FILEFORMAT_FNT 1 +#define SUPPORT_FILEFORMAT_TTF 1 + +// Support text management functions +// If not defined, still some functions are supported: TextLength(), TextFormat() +#define SUPPORT_TEXT_MANIPULATION 1 + +// text: Configuration values +//------------------------------------------------------------------------------------ +#define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions: + // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal(), TextSplit() +#define MAX_TEXT_UNICODE_CHARS 512 // Maximum number of unicode codepoints: GetCodepoints() +#define MAX_TEXTSPLIT_COUNT 128 // Maximum number of substrings to split: TextSplit() + + +//------------------------------------------------------------------------------------ +// Module: models - Configuration Flags +//------------------------------------------------------------------------------------ +// Selected desired model fileformats to be supported for loading +#define SUPPORT_FILEFORMAT_OBJ 1 +#define SUPPORT_FILEFORMAT_MTL 1 +#define SUPPORT_FILEFORMAT_IQM 1 +#define SUPPORT_FILEFORMAT_GLTF 1 +// Support procedural mesh generation functions, uses external par_shapes.h library +// NOTE: Some generated meshes DO NOT include generated texture coordinates +#define SUPPORT_MESH_GENERATION 1 + + +//------------------------------------------------------------------------------------ +// Module: audio - Configuration Flags +//------------------------------------------------------------------------------------ +// Desired audio fileformats to be supported for loading +#define SUPPORT_FILEFORMAT_WAV 1 +#define SUPPORT_FILEFORMAT_OGG 1 +#define SUPPORT_FILEFORMAT_XM 1 +#define SUPPORT_FILEFORMAT_MOD 1 +#define SUPPORT_FILEFORMAT_MP3 1 +//#define SUPPORT_FILEFORMAT_FLAC 1 + +// audio: Configuration values +//------------------------------------------------------------------------------------ +#define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (miniaudio: float-32bit) +#define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo +#define AUDIO_DEVICE_SAMPLE_RATE 0 // Device sample rate (device default) + +#define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Maximum number of audio pool channels + +//------------------------------------------------------------------------------------ +// Module: utils - Configuration Flags +//------------------------------------------------------------------------------------ +// Standard file io library (stdio.h) included +#define SUPPORT_STANDARD_FILEIO +// Show TRACELOG() output messages +// NOTE: By default LOG_DEBUG traces not shown +#define SUPPORT_TRACELOG 1 +//#define SUPPORT_TRACELOG_DEBUG 1 + +// utils: Configuration values +//------------------------------------------------------------------------------------ +#define MAX_TRACELOG_MSG_LENGTH 128 // Max length of one trace-log message +#define MAX_UWP_MESSAGES 512 // Max UWP messages to process diff --git a/raylib_pi4_test/core.c b/raylib_pi4_test/core.c new file mode 100644 index 0000000..a6a8505 --- /dev/null +++ b/raylib_pi4_test/core.c @@ -0,0 +1,6611 @@ +/********************************************************************************************** +* +* raylib.core - Basic functions to manage windows, OpenGL context and input on multiple platforms +* +* PLATFORMS SUPPORTED: +* - PLATFORM_DESKTOP: Windows (Win32, Win64) +* - PLATFORM_DESKTOP: Linux (X11 desktop mode) +* - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) +* - PLATFORM_DESKTOP: OSX/macOS +* - PLATFORM_ANDROID: Android 4.0 (ARM, ARM64) +* - PLATFORM_RPI: Raspberry Pi 0,1,2,3,4 (Raspbian) +* - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver +* - PLATFORM_WEB: HTML5 with asm.js (Chrome, Firefox) +* - PLATFORM_UWP: Windows 10 App, Windows Phone, Xbox One +* +* CONFIGURATION: +* +* #define PLATFORM_DESKTOP +* Windowing and input system configured for desktop platforms: Windows, Linux, OSX, FreeBSD, OpenBSD, NetBSD, DragonFly +* NOTE: Oculus Rift CV1 requires PLATFORM_DESKTOP for mirror rendering - View [rlgl] module to enable it +* +* #define PLATFORM_ANDROID +* Windowing and input system configured for Android device, app activity managed internally in this module. +* NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL +* +* #define PLATFORM_RPI +* Windowing and input system configured for Raspberry Pi i native mode (no X.org required, tested on Raspbian), +* graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ +* +* #define PLATFORM_WEB +* Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js +* using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. +* +* #define PLATFORM_UWP +* Universal Windows Platform support, using OpenGL ES 2.0 through ANGLE on multiple Windows platforms, +* including Windows 10 App, Windows Phone and Xbox One platforms. +* +* #define SUPPORT_DEFAULT_FONT (default) +* Default font is loaded on window initialization to be available for the user to render simple text. +* NOTE: If enabled, uses external module functions to load default raylib font (module: text) +* +* #define SUPPORT_CAMERA_SYSTEM +* Camera module is included (camera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital +* +* #define SUPPORT_GESTURES_SYSTEM +* Gestures module is included (gestures.h) to support gestures detection: tap, hold, swipe, drag +* +* #define SUPPORT_MOUSE_GESTURES +* Mouse gestures are directly mapped like touches and processed by gestures system. +* +* #define SUPPORT_TOUCH_AS_MOUSE +* Touch input and mouse input are shared. Mouse functions also return touch information. +* +* #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) +* Reconfigure standard input to receive key inputs, works with SSH connection. +* WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other running processes or +* blocking the device is not restored properly. Use with care. +* +* #define SUPPORT_MOUSE_CURSOR_NATIVE (Raspberry Pi and DRM only) +* Draw a mouse pointer on screen +* +* #define SUPPORT_BUSY_WAIT_LOOP +* Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used +* +* #define SUPPORT_HALFBUSY_WAIT_LOOP +* Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end +* +* #define SUPPORT_EVENTS_WAITING +* Wait for events passively (sleeping while no events) instead of polling them actively every frame +* +* #define SUPPORT_SCREEN_CAPTURE +* Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() +* +* #define SUPPORT_GIF_RECORDING +* Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() +* +* #define SUPPORT_COMPRESSION_API +* Support CompressData() and DecompressData() functions, those functions use zlib implementation +* provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module +* for linkage +* +* #define SUPPORT_DATA_STORAGE +* Support saving binary data automatically to a generated storage.data file. This file is managed internally +* +* +* DEPENDENCIES: +* rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly) +* raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) +* camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) +* gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#else + #define RAYLIB_VERSION "3.7" +#endif + +#include "utils.h" // Required for: TRACELOG macros + +#if (defined(__linux__) || defined(PLATFORM_WEB)) && _POSIX_C_SOURCE < 199309L + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. +#endif + +#define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation of raymath here +#include "raymath.h" // Required for: Vector3 and Matrix functions + +#define RLGL_IMPLEMENTATION +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 + +#if defined(SUPPORT_GESTURES_SYSTEM) + #define GESTURES_IMPLEMENTATION + #include "gestures.h" // Gestures detection functionality +#endif + +#if defined(SUPPORT_CAMERA_SYSTEM) + #define CAMERA_IMPLEMENTATION + #include "camera.h" // Camera system functionality +#endif + +#if defined(SUPPORT_GIF_RECORDING) + //#define MSF_GIF_MALLOC RL_MALLOC + //#define MSF_GIF_FREE RL_FREE + + #define MSF_GIF_IMPL + #include "external/msf_gif.h" // Support GIF recording +#endif + +#if defined(SUPPORT_COMPRESSION_API) + #define SINFL_IMPLEMENTATION + #include "external/sinfl.h" + + #define SDEFL_IMPLEMENTATION + #include "external/sdefl.h" +#endif + +#include // Required for: srand(), rand(), atexit() +#include // Required for: sprintf() [Used in OpenURL()] +#include // Required for: strrchr(), strcmp(), strlen() +#include // Required for: time() [Used in InitTimer()] +#include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] + +#include // Required for: stat() [Used in GetFileModTime()] + +#if (defined(PLATFORM_DESKTOP) || defined(PLATFORM_UWP)) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) + #define DIRENT_MALLOC RL_MALLOC + #define DIRENT_FREE RL_FREE + + #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] +#else + #include // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] +#endif + +#if defined(_WIN32) + #include // Required for: _getch(), _chdir() + #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() + #define CHDIR _chdir + #include // Required for _access() [Used in FileExists()] +#else + #include // Required for: getch(), chdir() (POSIX), access() + #define GETCWD getcwd + #define CHDIR chdir +#endif + +#if defined(PLATFORM_DESKTOP) + #define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 + // NOTE: Already provided by rlgl implementation (on glad.h) + #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management + // NOTE: GLFW3 already includes gl.h (OpenGL) headers + + // Support retrieving native window handlers + #if defined(_WIN32) + #define GLFW_EXPOSE_NATIVE_WIN32 + #include "GLFW/glfw3native.h" // WARNING: It requires customization to avoid windows.h inclusion! + + #if defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) + // NOTE: Those functions require linking with winmm library + unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); + unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); + #endif + #endif + #if defined(__linux__) || defined(__FreeBSD__) + #include // Required for: timespec, nanosleep(), select() - POSIX + + //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type + //#define GLFW_EXPOSE_NATIVE_WAYLAND + //#define GLFW_EXPOSE_NATIVE_MIR + #include "GLFW/glfw3native.h" // Required for: glfwGetX11Window() + #endif + #if defined(__APPLE__) + #include // Required for: usleep() + + //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition + #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() + #endif +#endif + +#if defined(PLATFORM_ANDROID) + //#include // Android sensors functions (accelerometer, gyroscope, light...) + #include // Defines AWINDOW_FLAG_FULLSCREEN and others + #include // Defines basic app state struct and manages activity + + #include // Native platform windowing system interface + //#include // OpenGL ES 2.0 library (not required in this module) +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + #include // POSIX file control definitions - open(), creat(), fcntl() + #include // POSIX standard function definitions - read(), close(), STDIN_FILENO + #include // POSIX terminal control definitions - tcgetattr(), tcsetattr() + #include // POSIX threads management (inputs reading) + #include // POSIX directory browsing + + #include // UNIX System call for device-specific input/output operations - ioctl() + #include // Linux: KDSKBMODE, K_MEDIUMRAM constants definition + #include // Linux: Keycodes constants definition (KEY_A, ...) + #include // Linux: Joystick support library + +#if defined(PLATFORM_RPI) + #include "bcm_host.h" // Raspberry Pi VideoCore IV access functions +#endif + +#if defined(PLATFORM_DRM) + #include // Generic Buffer Management + #include // Direct Rendering Manager user-level library interface + #include // Direct Rendering Manager modesetting interface +#endif + + #include "EGL/egl.h" // Native platform windowing system interface + #include "EGL/eglext.h" // EGL extensions + //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module) +#endif + +#if defined(PLATFORM_UWP) + #include "EGL/egl.h" // Native platform windowing system interface + #include "EGL/eglext.h" // EGL extensions + //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module) + #include "uwp_events.h" // UWP bootstrapping functions +#endif + +#if defined(PLATFORM_WEB) + #define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL) + #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management + #include // Required for: timespec, nanosleep(), select() - POSIX + + #include // Emscripten library - LLVM to JavaScript compiler + #include // Emscripten HTML5 library +#endif + +#if defined(SUPPORT_COMPRESSION_API) + // NOTE: Those declarations require stb_image and stb_image_write definitions, included in textures module + unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); + char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen); +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event number + + #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) + #define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events +#endif + +#ifndef MAX_FILEPATH_LENGTH + #if defined(__linux__) + #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) + #else + #define MAX_FILEPATH_LENGTH 512 // Maximum length supported for filepaths + #endif +#endif + +#ifndef MAX_GAMEPADS + #define MAX_GAMEPADS 4 // Max number of gamepads supported +#endif +#ifndef MAX_GAMEPAD_AXIS + #define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) +#endif +#ifndef MAX_GAMEPAD_BUTTONS + #define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) +#endif +#ifndef MAX_TOUCH_POINTS + #define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported +#endif +#ifndef MAX_KEY_PRESSED_QUEUE + #define MAX_KEY_PRESSED_QUEUE 16 // Max number of keys in the key input queue +#endif +#ifndef MAX_CHAR_PRESSED_QUEUE + #define MAX_CHAR_PRESSED_QUEUE 16 // Max number of characters in the char input queue +#endif + +#if defined(SUPPORT_DATA_STORAGE) + #ifndef STORAGE_DATA_FILE + #define STORAGE_DATA_FILE "storage.data" // Automatic storage filename + #endif +#endif + +#ifndef MAX_DECOMPRESSION_SIZE + #define MAX_DECOMPRESSION_SIZE 64 // Max size allocated for decompression in MB +#endif + +// Flags operation macros +#define FLAG_SET(n, f) ((n) |= (f)) +#define FLAG_CLEAR(n, f) ((n) &= ~(f)) +#define FLAG_TOGGLE(n, f) ((n) ^= (f)) +#define FLAG_CHECK(n, f) ((n) & (f)) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +typedef struct { + pthread_t threadId; // Event reading thread id + int fd; // File descriptor to the device it is assigned to + int eventNum; // Number of 'event' device + Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) + int touchSlot; // Hold the touch slot number of the currently being sent multitouch block + bool isMouse; // True if device supports relative X Y movements + bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH + bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH + bool isKeyboard; // True if device has letter keycodes + bool isGamepad; // True if device has gamepad buttons +} InputEventWorker; +#endif + +typedef struct { int x; int y; } Point; +typedef struct { unsigned int width; unsigned int height; } Size; + +// Core global state context data +typedef struct CoreData { + struct { +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + GLFWwindow *handle; // Native window handle (graphic device) +#endif +#if defined(PLATFORM_RPI) + EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device) +#endif +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) +#if defined(PLATFORM_DRM) + int fd; // /dev/dri/... file descriptor + drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector + int modeIndex; // index of the used mode of connector->modes + drmModeCrtc *crtc; // crt controller + struct gbm_device *gbmDevice; // device of Generic Buffer Management (GBM, native platform for EGL on DRM) + struct gbm_surface *gbmSurface; // surface of GBM + struct gbm_bo *prevBO; // previous used GBM buffer object (during frame swapping) + uint32_t prevFB; // previous used GBM framebufer (during frame swapping) +#endif + EGLDisplay device; // Native display device (physical screen connection) + EGLSurface surface; // Surface to draw on, framebuffers (connected to context) + EGLContext context; // Graphic context, mode in which drawing can be done + EGLConfig config; // Graphic config +#endif + const char *title; // Window text title const pointer + unsigned int flags; // Configuration flags (bit based), keeps window state + bool ready; // Check if window has been initialized successfully + bool fullscreen; // Check if fullscreen mode is enabled + bool shouldClose; // Check if window set for closing + bool resizedLastFrame; // Check if window has been resized last frame + + Point position; // Window position on screen (required on fullscreen toggle) + Size display; // Display width and height (monitor, device-screen, LCD, ...) + Size screen; // Screen width and height (used render area) + Size currentFbo; // Current render width and height, it could change on BeginTextureMode() + Size render; // Framebuffer width and height (render area, including black bars if required) + Point renderOffset; // Offset from render area (must be divided by 2) + Matrix screenScale; // Matrix to scale screen (framebuffer rendering) + + char **dropFilesPath; // Store dropped files paths as strings + int dropFilesCount; // Count dropped files strings + + } Window; +#if defined(PLATFORM_ANDROID) + struct { + bool appEnabled; // Flag to detect if app is active ** = true + struct android_app *app; // Android activity + struct android_poll_source *source; // Android events polling source + const char *internalDataPath; // Android internal data path to write data (/data/data//files) + bool contextRebindRequired; // Used to know context rebind required + } Android; +#endif +#if defined(PLATFORM_UWP) + struct { + const char *internalDataPath; // UWP App data path + } UWP; +#endif + struct { +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event" +#endif + struct { + int exitKey; // Default exit key + char currentKeyState[512]; // Registers current frame key state + char previousKeyState[512]; // Registers previous frame key state + + int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue + int keyPressedQueueCount; // Input keys queue count + + int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue + int charPressedQueueCount; // Input characters queue count + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + int defaultMode; // Default keyboard mode + struct termios defaultSettings; // Default keyboard settings + int fd; // File descriptor for the evdev keyboard +#endif + } Keyboard; + struct { + Vector2 position; // Mouse position on screen + Vector2 offset; // Mouse offset + Vector2 scale; // Mouse scaling + + int cursor; // Tracks current mouse cursor + bool cursorHidden; // Track if cursor is hidden + bool cursorOnScreen; // Tracks if cursor is inside client area + + char currentButtonState[3]; // Registers current mouse button state + char previousButtonState[3]; // Registers previous mouse button state + float currentWheelMove; // Registers current mouse wheel variation + float previousWheelMove; // Registers previous mouse wheel variation +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + char currentButtonStateEvdev[3]; // Holds the new mouse state for the next polling event to grab (Can't be written directly due to multithreading, app could miss the update) +#endif + } Mouse; + struct { + Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen + char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state + char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state + } Touch; + struct { + int lastButtonPressed; // Register last gamepad button pressed + int axisCount; // Register number of available gamepad axis + bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready + float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state + char currentState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state + char previousState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + pthread_t threadId; // Gamepad reading thread id + int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor + char name[64]; // Gamepad name holder +#endif + } Gamepad; + } Input; + struct { + double current; // Current time measure + double previous; // Previous time measure + double update; // Time measure for frame update + double draw; // Time measure for frame draw + double frame; // Time measure for one frame + double target; // Desired time for one frame, if 0 not applied +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + unsigned long long base; // Base time measure for hi-res timer +#endif + } Time; +} CoreData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static CoreData CORE = { 0 }; // Global CORE state context + +static char **dirFilesPath = NULL; // Store directory files paths as strings +static int dirFilesCount = 0; // Count directory files strings + +#if defined(SUPPORT_SCREEN_CAPTURE) +static int screenshotCounter = 0; // Screenshots counter +#endif + +#if defined(SUPPORT_GIF_RECORDING) +static int gifFramesCounter = 0; // GIF frames counter +static bool gifRecording = false; // GIF recording state +static MsfGifState gifState = { 0 }; // MSGIF context state +#endif +//----------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by core) +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_DEFAULT_FONT) +extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() +extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static bool InitGraphicsDevice(int width, int height); // Initialize graphics device +static void SetupFramebuffer(int width, int height); // Setup main framebuffer +static void SetupViewport(int width, int height); // Set viewport for a provided width and height +static void SwapBuffers(void); // Copy back buffer to front buffer + +static void InitTimer(void); // Initialize timer +static void Wait(float ms); // Wait for some milliseconds (stop program execution) + +static void PollInputEvents(void); // Register user events + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) +static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error +// Window callbacks events +static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +#if !defined(PLATFORM_WEB) +static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized +#endif +static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window +// Input callbacks events +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed +static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed +static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel +static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area +#endif + +#if defined(PLATFORM_ANDROID) +static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs +#endif + +#if defined(PLATFORM_WEB) +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(SUPPORT_SSH_KEYBOARD_RPI) +static void InitKeyboard(void); // Init raw keyboard system (standard input reading) +static void ProcessKeyboard(void); // Process keyboard events +static void RestoreKeyboard(void); // Restore keyboard system +#else +static void InitTerminal(void); // Init terminal (block echo and signal short cuts) +static void RestoreTerminal(void); // Restore terminal +#endif + +static void InitEvdevInput(void); // Evdev inputs initialization +static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate +static void PollKeyboardEvents(void); // Process evdev keyboard events. +static void *EventThread(void *arg); // Input device events reading thread + +static void InitGamepad(void); // Init raw gamepad input +static void *GamepadThread(void *arg); // Mouse reading thread + +#if defined(PLATFORM_DRM) +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list +#endif + +#endif // PLATFORM_RPI || PLATFORM_DRM + +#if defined(_WIN32) + // NOTE: We include Sleep() function signature here to avoid windows.h inclusion (kernel32 lib) + void __stdcall Sleep(unsigned long msTimeout); // Required for Wait() +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Window and OpenGL Context Functions +//---------------------------------------------------------------------------------- + +#if defined(PLATFORM_ANDROID) +// To allow easier porting to android, we allow the user to define a +// main function which we call from android_main, defined by ourselves +extern int main(int argc, char *argv[]); + +void android_main(struct android_app *app) +{ + char arg0[] = "raylib"; // NOTE: argv[] are mutable + CORE.Android.app = app; + + // TODO: Should we maybe report != 0 return codes somewhere? + (void)main(1, (char *[]) { arg0, NULL }); +} + +// TODO: Add this to header (if apps really need it) +struct android_app *GetAndroidApp(void) +{ + return CORE.Android.app; +} +#endif +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && !defined(SUPPORT_SSH_KEYBOARD_RPI) +// Init terminal (block echo and signal short cuts) +static void InitTerminal(void) +{ + TRACELOG(LOG_INFO, "RPI: Reconfiguring terminal..."); + + // Save terminal keyboard settings and reconfigure terminal with new settings + struct termios keyboardNewSettings; + tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings + keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; + + // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo + // NOTE: ISIG controls if ^C and ^Z generate break signals or not + keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); + keyboardNewSettings.c_cc[VMIN] = 1; + keyboardNewSettings.c_cc[VTIME] = 0; + + // Set new keyboard settings (change occurs immediately) + tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); + + // Save old keyboard mode to restore it at the end + if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0) + { + // NOTE: It could mean we are using a remote keyboard through ssh or from the desktop + TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (not a local terminal)"); + } + else ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); + + // Register terminal restore when program finishes + atexit(RestoreTerminal); +} +// Restore terminal +static void RestoreTerminal(void) +{ + TRACELOG(LOG_INFO, "RPI: Restoring terminal..."); + + // Reset to default keyboard settings + tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); + + // Reconfigure keyboard to default mode + ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); +} +#endif +// Initialize window and OpenGL context +// NOTE: data parameter could be used to pass any kind of required data to the initialization +void InitWindow(int width, int height, const char *title) +{ +#if defined(PLATFORM_UWP) + if (!UWPIsConfigured()) + { + TRACELOG(LOG_ERROR, "UWP Functions have not been set yet, please set these before initializing raylib!"); + return; + } +#endif + + TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); + + if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; + + // Initialize required global values different than 0 + CORE.Input.Keyboard.exitKey = KEY_ESCAPE; + CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; + CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; + CORE.Input.Gamepad.lastButtonPressed = -1; + +#if defined(PLATFORM_UWP) + // The axis count is 6 (2 thumbsticks and left and right trigger) + CORE.Input.Gamepad.axisCount = 6; +#endif + +#if defined(PLATFORM_ANDROID) + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + + // Input data is android app pointer + CORE.Android.internalDataPath = CORE.Android.app->activity->internalDataPath; + + // Set desired windows flags before initializing anything + ANativeActivity_setWindowFlags(CORE.Android.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER + + int orientation = AConfiguration_getOrientation(CORE.Android.app->config); + + if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait"); + else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape"); + + // TODO: Automatic orientation doesn't seem to work + if (width <= height) + { + AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_PORT); + TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait"); + } + else + { + AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_LAND); + TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape"); + } + + //AConfiguration_getDensity(CORE.Android.app->config); + //AConfiguration_getKeyboard(CORE.Android.app->config); + //AConfiguration_getScreenSize(CORE.Android.app->config); + //AConfiguration_getScreenLong(CORE.Android.app->config); + + CORE.Android.app->onAppCmd = AndroidCommandCallback; + CORE.Android.app->onInputEvent = AndroidInputCallback; + + InitAssetManager(CORE.Android.app->activity->assetManager, CORE.Android.app->activity->internalDataPath); + + TRACELOG(LOG_INFO, "ANDROID: App initialized successfully"); + + // Android ALooper_pollAll() variables + int pollResult = 0; + int pollEvents = 0; + + // Wait for window to be initialized (display and context) + while (!CORE.Window.ready) + { + // Process events loop + while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) + { + // Process this event + if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); + + // NOTE: Never close window, native activity is controlled by the system! + //if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true; + } + } +#endif +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) || defined(PLATFORM_DRM) + // Init graphics device (display device and OpenGL context) + // NOTE: returns true if window and graphic device has been initialized successfully + CORE.Window.ready = InitGraphicsDevice(width, height); + + if (!CORE.Window.ready) return; + + // Init hi-res timer + InitTimer(); + +#if defined(SUPPORT_DEFAULT_FONT) + // Load default font + // NOTE: External functions (defined in module: text) + LoadFontDefault(); + Rectangle rec = GetFontDefault().recs[95]; + // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); +#else + // Set default internal texture (1px white) and rectangle to be used for shapes drawing + SetShapesTexture(rlGetTextureDefault(), (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); +#endif +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // Set default font texture filter for HighDPI (blurry) + SetTextureFilter(GetFontDefault().texture, TEXTURE_FILTER_BILINEAR); + } +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + // Init raw input system + InitEvdevInput(); // Evdev inputs initialization + InitGamepad(); // Gamepad init +#if defined(SUPPORT_SSH_KEYBOARD_RPI) + InitKeyboard(); // Keyboard init +#else + InitTerminal(); // Terminal init +#endif +#endif + +#if defined(PLATFORM_WEB) + // Detect fullscreen change events + //emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); + //emscripten_set_resize_callback("#canvas", NULL, 1, EmscriptenResizeCallback); + + // Support keyboard events + //emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); + //emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); + + // Support touch events + emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenTouchCallback); + + // Support gamepad events (not provided by GLFW3 on emscripten) + emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); + emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); +#endif + + CORE.Input.Mouse.position.x = (float)CORE.Window.screen.width/2.0f; + CORE.Input.Mouse.position.y = (float)CORE.Window.screen.height/2.0f; +#endif // PLATFORM_ANDROID +} + +// Close window and unload OpenGL context +void CloseWindow(void) +{ +#if defined(SUPPORT_GIF_RECORDING) + if (gifRecording) + { + MsfGifResult result = msf_gif_end(&gifState); + msf_gif_free(result); + gifRecording = false; + } +#endif + +#if defined(SUPPORT_DEFAULT_FONT) + UnloadFontDefault(); +#endif + + rlglClose(); // De-init rlgl + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + glfwDestroyWindow(CORE.Window.handle); + glfwTerminate(); +#endif + +#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) + timeEndPeriod(1); // Restore time period +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) +#if defined(PLATFORM_DRM) + if (CORE.Window.prevFB) + { + drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); + CORE.Window.prevFB = 0; + } + + if (CORE.Window.prevBO) + { + gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); + CORE.Window.prevBO = NULL; + } + + if (CORE.Window.gbmSurface) + { + gbm_surface_destroy(CORE.Window.gbmSurface); + CORE.Window.gbmSurface = NULL; + } + + if (CORE.Window.gbmDevice) + { + gbm_device_destroy(CORE.Window.gbmDevice); + CORE.Window.gbmDevice = NULL; + } + + if (CORE.Window.crtc) + { + if (CORE.Window.connector) + { + drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, CORE.Window.crtc->buffer_id, + CORE.Window.crtc->x, CORE.Window.crtc->y, &CORE.Window.connector->connector_id, 1, &CORE.Window.crtc->mode); + drmModeFreeConnector(CORE.Window.connector); + CORE.Window.connector = NULL; + } + + drmModeFreeCrtc(CORE.Window.crtc); + CORE.Window.crtc = NULL; + } + + if (CORE.Window.fd != -1) + { + close(CORE.Window.fd); + CORE.Window.fd = -1; + } +#endif + + // Close surface, context and display + if (CORE.Window.device != EGL_NO_DISPLAY) + { +#if !defined(PLATFORM_DRM) + eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +#endif + if (CORE.Window.surface != EGL_NO_SURFACE) + { + eglDestroySurface(CORE.Window.device, CORE.Window.surface); + CORE.Window.surface = EGL_NO_SURFACE; + } + + if (CORE.Window.context != EGL_NO_CONTEXT) + { + eglDestroyContext(CORE.Window.device, CORE.Window.context); + CORE.Window.context = EGL_NO_CONTEXT; + } + + eglTerminate(CORE.Window.device); + CORE.Window.device = EGL_NO_DISPLAY; + } +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + // Wait for mouse and gamepad threads to finish before closing + // NOTE: Those threads should already have finished at this point + // because they are controlled by CORE.Window.shouldClose variable + + CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called + + // Close the evdev keyboard + if (CORE.Input.Keyboard.fd != -1) + { + close(CORE.Input.Keyboard.fd); + CORE.Input.Keyboard.fd = -1; + } + + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].threadId) + { + pthread_join(CORE.Input.eventWorker[i].threadId, NULL); + } + } + + + if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL); +#endif + + TRACELOG(LOG_INFO, "Window closed successfully"); +} + +// Check if KEY_ESCAPE pressed or Close icon pressed +bool WindowShouldClose(void) +{ +#if defined(PLATFORM_WEB) + // Emterpreter-Async required to run sync code + // https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code + // By default, this function is never called on a web-ready raylib example because we encapsulate + // frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously + // but now emscripten allows sync code to be executed in an interpreted way, using emterpreter! + emscripten_sleep(16); + return false; +#endif + +#if defined(PLATFORM_DESKTOP) + if (CORE.Window.ready) + { + // While window minimized, stop loop execution + while (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN)) glfwWaitEvents(); + + CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle); + + // Reset close status for next frame + glfwSetWindowShouldClose(CORE.Window.handle, GLFW_FALSE); + + return CORE.Window.shouldClose; + } + else return true; +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +#endif +} + +// Check if window has been initialized successfully +bool IsWindowReady(void) +{ + return CORE.Window.ready; +} + +// Check if window is currently fullscreen +bool IsWindowFullscreen(void) +{ + return CORE.Window.fullscreen; +} + +// Check if window is currently hidden +bool IsWindowHidden(void) +{ +#if defined(PLATFORM_DESKTOP) + return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0); +#endif + return false; +} + +// Check if window has been minimized +bool IsWindowMinimized(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0); +#else + return false; +#endif +} + +// Check if window has been maximized (only PLATFORM_DESKTOP) +bool IsWindowMaximized(void) +{ +#if defined(PLATFORM_DESKTOP) + return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0); +#else + return false; +#endif +} + +// Check if window has the focus +bool IsWindowFocused(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); // TODO! +#else + return true; +#endif +} + +// Check if window has been resizedLastFrame +bool IsWindowResized(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return CORE.Window.resizedLastFrame; +#else + return false; +#endif +} + +// Check if one specific window flag is enabled +bool IsWindowState(unsigned int flag) +{ + return ((CORE.Window.flags & flag) > 0); +} + +// Toggle fullscreen mode (only PLATFORM_DESKTOP) +void ToggleFullscreen(void) +{ +#if defined(PLATFORM_DESKTOP) + // NOTE: glfwSetWindowMonitor() doesn't work properly (bugs) + if (!CORE.Window.fullscreen) + { + // Store previous window position (in case we exit fullscreen) + glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y); + + int monitorCount = 0; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + + int monitorIndex = GetCurrentMonitor(); + + // Use current monitor, so we correctly get the display the window is on + GLFWmonitor* monitor = monitorIndex < monitorCount ? monitors[monitorIndex] : NULL; + + if (!monitor) + { + TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); + + CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + return; + } + + CORE.Window.fullscreen = true; // Toggle fullscreen flag + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + else + { + CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration + if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); +#endif +#if defined(PLATFORM_WEB) + EM_ASM + ( + // This strategy works well while using raylib minimal web shell for emscripten, + // it re-scales the canvas to fullscreen using monitor resolution, for tools this + // is a good strategy but maybe games prefer to keep current canvas resolution and + // display it in fullscreen, adjusting monitor resolution if possible + if (document.fullscreenElement) document.exitFullscreen(); + else Module.requestFullscreen(false, true); + ); + +/* + if (!CORE.Window.fullscreen) + { + // Option 1: Request fullscreen for the canvas element + // This option does not seem to work at all + //emscripten_request_fullscreen("#canvas", false); + + // Option 2: Request fullscreen for the canvas element with strategy + // This option does not seem to work at all + // Ref: https://github.com/emscripten-core/emscripten/issues/5124 + // EmscriptenFullscreenStrategy strategy = { + // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + // .canvasResizedCallback = EmscriptenWindowResizedCallback, + // .canvasResizedCallbackUserData = NULL + // }; + //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); + + // Option 3: Request fullscreen for the canvas element with strategy + // It works as expected but only inside the browser (client area) + EmscriptenFullscreenStrategy strategy = { + .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + .canvasResizedCallback = EmscriptenWindowResizedCallback, + .canvasResizedCallbackUserData = NULL + }; + emscripten_enter_soft_fullscreen("#canvas", &strategy); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); + } + else + { + //emscripten_exit_fullscreen(); + emscripten_exit_soft_fullscreen(); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); + } +*/ + + CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag + CORE.Window.flags ^= FLAG_FULLSCREEN_MODE; +#endif +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode"); +#endif +} + +// Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +void MaximizeWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + glfwMaximizeWindow(CORE.Window.handle); + CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + } +#endif +} + +// Set window state: minimized (only PLATFORM_DESKTOP) +void MinimizeWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + // NOTE: Following function launches callback that sets appropiate flag! + glfwIconifyWindow(CORE.Window.handle); +#endif +} + +// Set window state: not minimized/maximized (only PLATFORM_DESKTOP) +void RestoreWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + // Restores the specified window if it was previously iconified (minimized) or maximized + glfwRestoreWindow(CORE.Window.handle); + CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + } +#endif +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ +#if defined(PLATFORM_DESKTOP) + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0)) + { + glfwSwapInterval(1); + CORE.Window.flags |= FLAG_VSYNC_HINT; + } + + // State change: FLAG_FULLSCREEN_MODE + if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE)) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_TRUE); + CORE.Window.flags |= FLAG_WINDOW_RESIZABLE; + } + + // State change: FLAG_WINDOW_UNDECORATED + if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE); + CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; + } + + // State change: FLAG_WINDOW_HIDDEN + if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + { + glfwHideWindow(CORE.Window.handle); + CORE.Window.flags |= FLAG_WINDOW_HIDDEN; + } + + // State change: FLAG_WINDOW_MINIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) != (flags & FLAG_WINDOW_MINIMIZED)) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + { + //GLFW_ICONIFIED + MinimizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + { + //GLFW_MAXIMIZED + MaximizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) != (flags & FLAG_WINDOW_UNFOCUSED)) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE); + CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; + } + + // State change: FLAG_WINDOW_TOPMOST + if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE); + CORE.Window.flags |= FLAG_WINDOW_TOPMOST; + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) != (flags & FLAG_WINDOW_ALWAYS_RUN)) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + { + CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) != (flags & FLAG_WINDOW_TRANSPARENT)) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) != (flags & FLAG_WINDOW_HIGHDPI)) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); + } + + // State change: FLAG_MSAA_4X_HINT + if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if (((CORE.Window.flags & FLAG_INTERLACED_HINT) != (flags & FLAG_INTERLACED_HINT)) && ((flags & FLAG_INTERLACED_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); + } +#endif +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ +#if defined(PLATFORM_DESKTOP) + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0)) + { + glfwSwapInterval(0); + CORE.Window.flags &= ~FLAG_VSYNC_HINT; + } + + // State change: FLAG_FULLSCREEN_MODE + if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_FALSE); + CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE; + } + + // State change: FLAG_WINDOW_UNDECORATED + if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE); + CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; + } + + // State change: FLAG_WINDOW_HIDDEN + if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + { + glfwShowWindow(CORE.Window.handle); + CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; + } + + // State change: FLAG_WINDOW_MINIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE); + CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; + } + + // State change: FLAG_WINDOW_TOPMOST + if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE); + CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST; + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + { + CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); + } + + // State change: FLAG_MSAA_4X_HINT + if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); + } +#endif +} + +// Set icon for window (only PLATFORM_DESKTOP) +// NOTE: Image must be in RGBA format, 8bit per channel +void SetWindowIcon(Image image) +{ +#if defined(PLATFORM_DESKTOP) + if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + GLFWimage icon[1] = { 0 }; + + icon[0].width = image.width; + icon[0].height = image.height; + icon[0].pixels = (unsigned char *)image.data; + + // NOTE 1: We only support one image icon + // NOTE 2: The specified image data is copied before this function returns + glfwSetWindowIcon(CORE.Window.handle, 1, icon); + } + else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format"); +#endif +} + +// Set title for window (only PLATFORM_DESKTOP) +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; +#if defined(PLATFORM_DESKTOP) + glfwSetWindowTitle(CORE.Window.handle, title); +#endif +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetWindowPos(CORE.Window.handle, x, y); +#endif +} + +// Set monitor for the current window (fullscreen mode) +void SetWindowMonitor(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); + + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); + glfwSetWindowMonitor(CORE.Window.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ +#if defined(PLATFORM_DESKTOP) + const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + glfwSetWindowSizeLimits(CORE.Window.handle, width, height, mode->width, mode->height); +#endif +} + +// Set window dimensions +// TODO: Issues on HighDPI scaling +void SetWindowSize(int width, int height) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetWindowSize(CORE.Window.handle, width, height); +#endif +#if defined(PLATFORM_WEB) + //emscripten_set_canvas_size(width, height); // DEPRECATED! + + // TODO: Below functions should be used to replace previous one but + // they do not seem to work properly + //emscripten_set_canvas_element_size("canvas", width, height); + //emscripten_set_element_css_size("canvas", width, height); +#endif +} + +// Get current screen width +int GetScreenWidth(void) +{ + return CORE.Window.currentFbo.width; +} + +// Get current screen height +int GetScreenHeight(void) +{ + return CORE.Window.currentFbo.height; +} + +// Get native window handle +void *GetWindowHandle(void) +{ +#if defined(PLATFORM_DESKTOP) && defined(_WIN32) + // NOTE: Returned handle is: void *HWND (windows.h) + return glfwGetWin32Window(CORE.Window.handle); +#endif +#if defined(__linux__) + // NOTE: Returned handle is: unsigned long Window (X.h) + // typedef unsigned long XID; + // typedef XID Window; + //unsigned long id = (unsigned long)glfwGetX11Window(window); + return NULL; // TODO: Find a way to return value... cast to void *? +#endif +#if defined(__APPLE__) + // NOTE: Returned handle is: (objc_object *) + return NULL; // TODO: return (void *)glfwGetCocoaWindow(window); +#endif + + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + glfwGetMonitors(&monitorCount); + return monitorCount; +#else + return 1; +#endif +} + +// Get number of monitors +int GetCurrentMonitor(void) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + GLFWmonitor* monitor = NULL; + + if (monitorCount == 1) // easy out + return 0; + + if (IsWindowFullscreen()) + { + monitor = glfwGetWindowMonitor(CORE.Window.handle); + for (int i = 0; i < monitorCount; i++) + { + if (monitors[i] == monitor) + return i; + } + return 0; + } + else + { + int x = 0; + int y = 0; + + glfwGetWindowPos(CORE.Window.handle, &x, &y); + + for (int i = 0; i < monitorCount; i++) + { + int mx = 0; + int my = 0; + + int width = 0; + int height = 0; + + monitor = monitors[i]; + glfwGetMonitorWorkarea(monitor, &mx, &my, &width, &height); + if (x >= mx && x <= (mx + width) && y >= my && y <= (my + height)) + return i; + } + } + return 0; +#else + return 0; +#endif +} + +// Get selected monitor width +Vector2 GetMonitorPosition(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int x, y; + glfwGetMonitorPos(monitors[monitor], &x, &y); + + return (Vector2){ (float)x, (float)y }; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (max available by monitor) +int GetMonitorWidth(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + + // We return the maximum resolution available, the last one in the modes array + if (count > 0) return modes[count - 1].width; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +// Get selected monitor width (max available by monitor) +int GetMonitorHeight(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + + // We return the maximum resolution available, the last one in the modes array + if (count > 0) return modes[count - 1].height; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int physicalWidth; + glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL); + return physicalWidth; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +// Get primary monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int physicalHeight; + glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight); + return physicalHeight; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return 0; +} + +int GetMonitorRefreshRate(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + const GLFWvidmode *vidmode = glfwGetVideoMode(monitors[monitor]); + return vidmode->refreshRate; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif +#if defined(PLATFORM_DRM) + if ((CORE.Window.connector) && (CORE.Window.modeIndex >= 0)) + { + return CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh; + } +#endif + return 0; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + int x = 0; + int y = 0; +#if defined(PLATFORM_DESKTOP) + glfwGetWindowPos(CORE.Window.handle, &x, &y); +#endif + return (Vector2){ (float)x, (float)y }; +} + +// Get window scale DPI factor +Vector2 GetWindowScaleDPI(void) +{ + Vector2 scale = { 1.0f, 1.0f }; + +#if defined(PLATFORM_DESKTOP) + float xdpi = 1.0; + float ydpi = 1.0; + Vector2 windowPos = GetWindowPosition(); + + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + // Check window monitor + for (int i = 0; i < monitorCount; i++) + { + glfwGetMonitorContentScale(monitors[i], &xdpi, &ydpi); + + int xpos, ypos, width, height; + glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height); + + if ((windowPos.x >= xpos) && (windowPos.x < xpos + width) && + (windowPos.y >= ypos) && (windowPos.y < ypos + height)) + { + scale.x = xdpi; + scale.y = ydpi; + break; + } + } +#endif + + return scale; +} + +// Get the human-readable, UTF-8 encoded name of the primary monitor +const char *GetMonitorName(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + return glfwGetMonitorName(monitors[monitor]); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return ""; +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ +#if defined(PLATFORM_DESKTOP) + return glfwGetClipboardString(CORE.Window.handle); +#else + return NULL; +#endif +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetClipboardString(CORE.Window.handle, text); +#endif +} + +// Show mouse cursor +void ShowCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseShowFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = false; +} + +// Hides mouse cursor +void HideCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseHideFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = true; +} + +// Check if cursor is not visible +bool IsCursorHidden(void) +{ + return CORE.Input.Mouse.cursorHidden; +} + +// Enables cursor (unlock cursor) +void EnableCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +#endif +#if defined(PLATFORM_WEB) + emscripten_exit_pointerlock(); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseUnlockFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = false; +} + +// Disables cursor (lock cursor) +void DisableCursor(void) +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +#endif +#if defined(PLATFORM_WEB) + emscripten_request_pointerlock("#canvas", 1); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseLockFunc()(); +#endif + CORE.Input.Mouse.cursorHidden = true; +} + +// Check if cursor is on the current screen. +bool IsCursorOnScreen(void) +{ + return CORE.Input.Mouse.cursorOnScreen; +} + +// Set background color (framebuffer clear color) +void ClearBackground(Color color) +{ + rlClearColor(color.r, color.g, color.b, color.a); // Set clear color + rlClearScreenBuffers(); // Clear current framebuffers +} + +// Setup canvas (framebuffer) to start drawing +void BeginDrawing(void) +{ + CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer() + CORE.Time.update = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + rlLoadIdentity(); // Reset current matrix (modelview) + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling + + //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 + // NOTE: Not required with OpenGL 3.3+ +} + +// End canvas drawing and swap buffers (double buffering) +void EndDrawing(void) +{ +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_MOUSE_CURSOR_NATIVE) + // On native mode we have no system mouse cursor, so, + // we draw a small rectangle for user reference + if (!CORE.Input.Mouse.cursorHidden) + { + DrawRectangle(CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y, 3, 3, MAROON); + } +#endif + + rlDrawRenderBatchActive(); // Update and draw internal render batch + +#if defined(SUPPORT_GIF_RECORDING) + #define GIF_RECORD_FRAMERATE 10 + + if (gifRecording) + { + gifFramesCounter++; + + // NOTE: We record one gif frame every 10 game frames + if ((gifFramesCounter%GIF_RECORD_FRAMERATE) == 0) + { + // Get image data for the current frame (from backbuffer) + // NOTE: This process is quite slow... :( + unsigned char *screenData = rlReadScreenPixels(CORE.Window.screen.width, CORE.Window.screen.height); + msf_gif_frame(&gifState, screenData, 10, 16, CORE.Window.screen.width*4); + + RL_FREE(screenData); // Free image data + } + + if (((gifFramesCounter/15)%2) == 1) + { + DrawCircle(30, CORE.Window.screen.height - 20, 10, RED); + DrawText("RECORDING", 50, CORE.Window.screen.height - 25, 10, MAROON); + } + + rlDrawRenderBatchActive(); // Update and draw internal render batch + } +#endif + + SwapBuffers(); // Copy back buffer to front buffer + + // Frame time control system + CORE.Time.current = GetTime(); + CORE.Time.draw = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + CORE.Time.frame = CORE.Time.update + CORE.Time.draw; + + // Wait for some milliseconds... + if (CORE.Time.frame < CORE.Time.target) + { + Wait((float)(CORE.Time.target - CORE.Time.frame)*1000.0f); + + CORE.Time.current = GetTime(); + double waitTime = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + CORE.Time.frame += waitTime; // Total frame time: update + draw + wait + } + + PollInputEvents(); // Poll user events +} + +// Initialize 2D mode with custom camera (2D) +void BeginMode2D(Camera2D camera) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlLoadIdentity(); // Reset current matrix (modelview) + + // Apply 2d camera transformation to modelview + rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); + + // Apply screen scaling if required + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); +} + +// Ends 2D mode with custom camera +void EndMode2D(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlLoadIdentity(); // Reset current matrix (modelview) + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required +} + +// Initializes 3D mode with custom camera (3D) +void BeginMode3D(Camera3D camera) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection + rlLoadIdentity(); // Reset current matrix (projection) + + float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; + + // NOTE: zNear and zFar values are important when computing depth buffer values + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Setup perspective projection + double top = RL_CULL_DISTANCE_NEAR*tan(camera.fovy*0.5*DEG2RAD); + double right = top*aspect; + + rlFrustum(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + // Setup orthographic projection + double top = camera.fovy/2.0; + double right = top*aspect; + + rlOrtho(-right, right, -top,top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + // Setup Camera view + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) + + rlEnableDepthTest(); // Enable DEPTH_TEST for 3D +} + +// Ends 3D mode and returns to default 2D orthographic mode +void EndMode3D(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPopMatrix(); // Restore previous matrix (projection) from matrix stack + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required + + rlDisableDepthTest(); // Disable DEPTH_TEST for 2D +} + +// Initializes render texture for drawing +void BeginTextureMode(RenderTexture2D target) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlEnableFramebuffer(target.id); // Enable render target + + // Set viewport to framebuffer size + rlViewport(0, 0, target.texture.width, target.texture.height); + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlLoadIdentity(); // Reset current matrix (projection) + + // Set orthographic projection to current framebuffer size + // NOTE: Configured top-left corner as (0, 0) + rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) + + // Setup current width/height for proper aspect ratio + // calculation when using BeginMode3D() + CORE.Window.currentFbo.width = target.texture.width; + CORE.Window.currentFbo.height = target.texture.height; +} + +// Ends drawing to render texture +void EndTextureMode(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlDisableFramebuffer(); // Disable render target (fbo) + + // Set viewport to default framebuffer size + SetupViewport(CORE.Window.render.width, CORE.Window.render.height); + + // Reset current fbo to screen size + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; +} + +// Begin custom shader mode +void BeginShaderMode(Shader shader) +{ + rlSetShader(shader); +} + +// End custom shader mode (returns to default shader) +void EndShaderMode(void) +{ + rlSetShader(rlGetShaderDefault()); +} + +// Begin blending mode (alpha, additive, multiplied) +// NOTE: Only 3 blending modes supported, default blend mode is alpha +void BeginBlendMode(int mode) +{ + rlSetBlendMode(mode); +} + +// End blending mode (reset to default: alpha blending) +void EndBlendMode(void) +{ + rlSetBlendMode(BLEND_ALPHA); +} + +// Begin scissor mode (define screen area for following drawing) +// NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left +void BeginScissorMode(int x, int y, int width, int height) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlEnableScissorTest(); + rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); +} + +// End scissor mode +void EndScissorMode(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + rlDisableScissorTest(); +} + +// Begin VR drawing configuration +void BeginVrStereoMode(VrStereoConfig config) +{ + rlEnableStereoRender(); + + // Set stereo render matrices + rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]); + rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]); +} + +// End VR drawing process (and desktop mirror) +void EndVrStereoMode(void) +{ + rlDisableStereoRender(); +} + +// Load VR stereo config for VR simulator device parameters +VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) +{ + VrStereoConfig config = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Compute aspect ratio + float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution; + + // Compute lens parameters + float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize; + config.leftLensCenter[0] = 0.25f + lensShift; + config.leftLensCenter[1] = 0.5f; + config.rightLensCenter[0] = 0.75f - lensShift; + config.rightLensCenter[1] = 0.5f; + config.leftScreenCenter[0] = 0.25f; + config.leftScreenCenter[1] = 0.5f; + config.rightScreenCenter[0] = 0.75f; + config.rightScreenCenter[1] = 0.5f; + + // Compute distortion scale parameters + // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] + float lensRadius = fabsf(-1.0f - 4.0f*lensShift); + float lensRadiusSq = lensRadius*lensRadius; + float distortionScale = device.lensDistortionValues[0] + + device.lensDistortionValues[1]*lensRadiusSq + + device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + + device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; + + float normScreenWidth = 0.5f; + float normScreenHeight = 1.0f; + config.scaleIn[0] = 2.0f/normScreenWidth; + config.scaleIn[1] = 2.0f/normScreenHeight/aspect; + config.scale[0] = normScreenWidth*0.5f/distortionScale; + config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale; + + // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) + // ...but with lens distortion it is increased (see Oculus SDK Documentation) + //float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? + float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); + + // Compute camera projection matrices + float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] + Matrix proj = MatrixPerspective(fovy, aspect, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + + config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); + config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); + + // Compute camera transformation matrices + // NOTE: Camera movement might seem more natural if we model the head. + // Our axis of rotation is the base of our head, so we might want to add + // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. + config.viewOffset[0] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + config.viewOffset[1] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + + // Compute eyes Viewports + /* + config.eyeViewportRight[0] = 0; + config.eyeViewportRight[1] = 0; + config.eyeViewportRight[2] = device.hResolution/2; + config.eyeViewportRight[3] = device.vResolution; + + config.eyeViewportLeft[0] = device.hResolution/2; + config.eyeViewportLeft[1] = 0; + config.eyeViewportLeft[2] = device.hResolution/2; + config.eyeViewportLeft[3] = device.vResolution; + */ +#else + TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); +#endif + + return config; +} + +// Unload VR stereo config properties +void UnloadVrStereoConfig(VrStereoConfig config) +{ + //... +} + +// Load shader from files and bind default locations +// NOTE: If shader string is NULL, using default vertex/fragment shaders +Shader LoadShader(const char *vsFileName, const char *fsFileName) +{ + Shader shader = { 0 }; + shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + + char *vShaderStr = NULL; + char *fShaderStr = NULL; + + if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); + if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); + + shader.id = rlLoadShaderCode(vShaderStr, fShaderStr); + + if (vShaderStr != NULL) RL_FREE(vShaderStr); + if (fShaderStr != NULL) RL_FREE(fShaderStr); + + // After shader loading, we TRY to set default location names + if (shader.id > 0) + { + // Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 + + // NOTE: If any location is not found, loc point becomes -1 + + // Get handles to GLSL input attibute locations + shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Get handles to GLSL uniform locations (vertex shader) + shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, "mvp"); + shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, "view"); + shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, "projection"); + shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, "matNormal"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, "colDiffuse"); + shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, "texture0"); + shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, "texture1"); + shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, "texture2"); + } + + return shader; +} + +// Load shader from code strings and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) +{ + Shader shader = { 0 }; + shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + shader.id = rlLoadShaderCode(vsCode, fsCode); + + // After shader loading, we TRY to set default location names + if (shader.id > 0) + { + // Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 + + // NOTE: If any location is not found, loc point becomes -1 + + // Get handles to GLSL input attibute locations + shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Get handles to GLSL uniform locations (vertex shader) + shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, "mvp"); + shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, "projection"); + shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, "view"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, "colDiffuse"); + shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, "texture0"); + shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, "texture1"); + shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, "texture2"); + } + + return shader; +} + +// Unload shader from GPU memory (VRAM) +void UnloadShader(Shader shader) +{ + if (shader.id != rlGetShaderDefault().id) + { + rlUnloadShaderProgram(shader.id); + RL_FREE(shader.locs); + } +} + +// Get shader uniform location +int GetShaderLocation(Shader shader, const char *uniformName) +{ + return rlGetLocationUniform(shader.id, uniformName); +} + +// Get shader attribute location +int GetShaderLocationAttrib(Shader shader, const char *attribName) +{ + return rlGetLocationAttrib(shader.id, attribName); +} + +// Set shader uniform value +void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType) +{ + SetShaderValueV(shader, locIndex, value, uniformType, 1); +} + +// Set shader uniform value vector +void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count) +{ + rlEnableShader(shader.id); + rlSetUniform(locIndex, value, uniformType, count); + //rlDisableShader(); // Avoid reseting current shader program, in case other uniforms are set +} + +// Set shader uniform value (matrix 4x4) +void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat) +{ + rlEnableShader(shader.id); + rlSetUniformMatrix(locIndex, mat); + //rlDisableShader(); +} + +// Set shader uniform value for texture +void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture) +{ + rlEnableShader(shader.id); + rlSetUniformSampler(locIndex, texture.id); + //rlDisableShader(); +} + +// Returns a ray trace from mouse position +Ray GetMouseRay(Vector2 mouse, Camera camera) +{ + Ray ray; + + // Calculate normalized device coordinates + // NOTE: y value is negative + float x = (2.0f*mouse.x)/(float)GetScreenWidth() - 1.0f; + float y = 1.0f - (2.0f*mouse.y)/(float)GetScreenHeight(); + float z = 1.0f; + + // Store values in a vector + Vector3 deviceCoords = { x, y, z }; + + // Calculate view matrix from camera look at + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + Matrix matProj = MatrixIdentity(); + + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + double top = camera.fovy/2.0; + double right = top*aspect; + + // Calculate projection matrix from orthographic + matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); + } + + // Unproject far/near points + Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); + Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); + + // Unproject the mouse cursor in the near plane. + // We need this as the source position because orthographic projects, compared to perspect doesn't have a + // convergence point, meaning that the "eye" of the camera is more like a plane than a point. + Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); + + // Calculate normalized direction vector + Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); + + if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position; + else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; + + // Apply calculated vectors to ray + ray.direction = direction; + + return ray; +} + +// Get transform matrix for camera +Matrix GetCameraMatrix(Camera camera) +{ + return MatrixLookAt(camera.position, camera.target, camera.up); +} + +// Returns camera 2d transform matrix +Matrix GetCameraMatrix2D(Camera2D camera) +{ + Matrix matTransform = { 0 }; + // The camera in world-space is set by + // 1. Move it to target + // 2. Rotate by -rotation and scale by (1/zoom) + // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), + // not for the camera getting bigger, hence the invert. Same deal with rotation. + // 3. Move it by (-offset); + // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) + // we need to do it into opposite direction (inverse transform) + + // Having camera transform in world-space, inverse of it gives the modelview transform. + // Since (A*B*C)' = C'*B'*A', the modelview is + // 1. Move to offset + // 2. Rotate and Scale + // 3. Move by -target + Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); + Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); + Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); + Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); + + matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); + + return matTransform; +} + +// Returns the screen space position from a 3d world space position +Vector2 GetWorldToScreen(Vector3 position, Camera camera) +{ + Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight()); + + return screenPosition; +} + +// Returns size position for a 3d world space position (useful for texture drawing) +Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) +{ + // Calculate projection matrix (from perspective instead of frustum + Matrix matProj = MatrixIdentity(); + + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + double top = camera.fovy/2.0; + double right = top*aspect; + + // Calculate projection matrix from orthographic + matProj = MatrixOrtho(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + } + + // Calculate view matrix from camera look at (and transpose it) + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + // Convert world position vector to quaternion + Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; + + // Transform world position to view + worldPos = QuaternionTransform(worldPos, matView); + + // Transform result to projection (clip space position) + worldPos = QuaternionTransform(worldPos, matProj); + + // Calculate normalized device coordinates (inverted y) + Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w }; + + // Calculate 2d screen position vector + Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height }; + + return screenPosition; +} + +// Returns the screen space position for a 2d camera world space position +Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) +{ + Matrix matCamera = GetCameraMatrix2D(camera); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); + + return (Vector2){ transform.x, transform.y }; +} + +// Returns the world space position for a 2d camera screen space position +Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) +{ + Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); + + return (Vector2){ transform.x, transform.y }; +} + +// Set target FPS (maximum) +void SetTargetFPS(int fps) +{ + if (fps < 1) CORE.Time.target = 0.0; + else CORE.Time.target = 1.0/(double)fps; + + TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000); +} + +// Returns current FPS +// NOTE: We calculate an average framerate +int GetFPS(void) +{ + #define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures + #define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 millisecondes + #define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT) + + static int index = 0; + static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 }; + static float average = 0, last = 0; + float fpsFrame = GetFrameTime(); + + if (fpsFrame == 0) return 0; + + if ((GetTime() - last) > FPS_STEP) + { + last = (float)GetTime(); + index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT; + average -= history[index]; + history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT; + average += history[index]; + } + + return (int)roundf(1.0f/average); +} + +// Returns time in seconds for last frame drawn (delta time) +float GetFrameTime(void) +{ + return (float)CORE.Time.frame; +} + +// Get elapsed time measure in seconds since InitTimer() +// NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow() +// NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit() +double GetTime(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + return glfwGetTime(); // Elapsed time since glfwInit() +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + + return (double)(time - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() +#endif + +#if defined(PLATFORM_UWP) + return UWPGetQueryTimeFunc()(); +#endif +} + +// Setup window configuration flags (view FLAGS) +// NOTE: This function is expected to be called before window creation, +// because it setups some flags for the window creation process. +// To configure window states after creation, just use SetWindowState() +void SetConfigFlags(unsigned int flags) +{ + // Selected flags are set but not evaluated at this point, + // flag evaluation happens at InitWindow() or SetWindowState() + CORE.Window.flags |= flags; +} + +// NOTE TRACELOG() function is located in [utils.h] + +// Takes a screenshot of current screen (saved a .png) +// NOTE: This function could work in any platform but some platforms: PLATFORM_ANDROID and PLATFORM_WEB +// have their own internal file-systems, to dowload image to user file-system some additional mechanism is required +void TakeScreenshot(const char *fileName) +{ + unsigned char *imgData = rlReadScreenPixels(CORE.Window.render.width, CORE.Window.render.height); + Image image = { imgData, CORE.Window.render.width, CORE.Window.render.height, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + + char path[512] = { 0 }; +#if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, "/"); + strcat(path, fileName); +#elif defined(PLATFORM_UWP) + strcpy(path, CORE.UWP.internalDataPath); + strcat(path, "/"); + strcat(path, fileName); +#else + strcpy(path, fileName); +#endif + + ExportImage(image, path); + RL_FREE(imgData); + +#if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", GetFileName(path), GetFileName(path))); +#endif + + // TODO: Verification required for log + TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); +} + +// Returns a random value between min and max (both included) +int GetRandomValue(int min, int max) +{ + if (min > max) + { + int tmp = max; + max = min; + min = tmp; + } + + return (rand()%(abs(max - min) + 1) + min); +} + +// Check if the file exists +bool FileExists(const char *fileName) +{ + bool result = false; + +#if defined(_WIN32) + if (_access(fileName, 0) != -1) result = true; +#else + if (access(fileName, F_OK) != -1) result = true; +#endif + + return result; +} + +// Check file extension +// NOTE: Extensions checking is not case-sensitive +bool IsFileExtension(const char *fileName, const char *ext) +{ + bool result = false; + const char *fileExt = GetFileExtension(fileName); + + if (fileExt != NULL) + { +#if defined(SUPPORT_TEXT_MANIPULATION) + int extCount = 0; + const char **checkExts = TextSplit(ext, ';', &extCount); + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileExt)); + + for (int i = 0; i < extCount; i++) + { + if (TextIsEqual(fileExtLower, TextToLower(checkExts[i]))) + { + result = true; + break; + } + } +#else + if (strcmp(fileExt, ext) == 0) result = true; +#endif + } + + return result; +} + +// Check if a directory path exists +bool DirectoryExists(const char *dirPath) +{ + bool result = false; + DIR *dir = opendir(dirPath); + + if (dir != NULL) + { + result = true; + closedir(dir); + } + + return result; +} + +// Get pointer to extension for a filename string (includes the dot: .png) +const char *GetFileExtension(const char *fileName) +{ + const char *dot = strrchr(fileName, '.'); + + if (!dot || dot == fileName) return NULL; + + return dot; +} + +// String pointer reverse break: returns right-most occurrence of charset in s +static const char *strprbrk(const char *s, const char *charset) +{ + const char *latestMatch = NULL; + for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } + return latestMatch; +} + +// Get pointer to filename for a path string +const char *GetFileName(const char *filePath) +{ + const char *fileName = NULL; + if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); + + if (!fileName) return filePath; + + return fileName + 1; +} + +// Get filename string without extension (uses static string) +const char *GetFileNameWithoutExt(const char *filePath) +{ + #define MAX_FILENAMEWITHOUTEXT_LENGTH 128 + + static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH]; + memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); + + if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension + + int len = (int)strlen(fileName); + + for (int i = 0; (i < len) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) + { + if (fileName[i] == '.') + { + // NOTE: We break on first '.' found + fileName[i] = '\0'; + break; + } + } + + return fileName; +} + +// Get directory for a given filePath +const char *GetDirectoryPath(const char *filePath) +{ +/* + // NOTE: Directory separator is different in Windows and other platforms, + // fortunately, Windows also support the '/' separator, that's the one should be used + #if defined(_WIN32) + char separator = '\\'; + #else + char separator = '/'; + #endif +*/ + const char *lastSlash = NULL; + static char dirPath[MAX_FILEPATH_LENGTH]; + memset(dirPath, 0, MAX_FILEPATH_LENGTH); + + // In case provided path does not contain a root drive letter (C:\, D:\) nor leading path separator (\, /), + // we add the current directory path to dirPath + if (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/') + { + // For security, we set starting path to current directory, + // obtained path will be concated to this + dirPath[0] = '.'; + dirPath[1] = '/'; + } + + lastSlash = strprbrk(filePath, "\\/"); + if (lastSlash) + { + if (lastSlash == filePath) + { + // The last and only slash is the leading one: path is in a root directory + dirPath[0] = filePath[0]; + dirPath[1] = '\0'; + } + else + { + // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' + memcpy(dirPath + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1)); + dirPath[strlen(filePath) - strlen(lastSlash) + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0)] = '\0'; // Add '\0' manually + } + } + + return dirPath; +} + +// Get previous directory path for a given path +const char *GetPrevDirectoryPath(const char *dirPath) +{ + static char prevDirPath[MAX_FILEPATH_LENGTH]; + memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); + int pathLen = (int)strlen(dirPath); + + if (pathLen <= 3) strcpy(prevDirPath, dirPath); + + for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--) + { + if ((dirPath[i] == '\\') || (dirPath[i] == '/')) + { + // Check for root: "C:\" or "/" + if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++; + + strncpy(prevDirPath, dirPath, i); + break; + } + } + + return prevDirPath; +} + +// Get current working directory +const char *GetWorkingDirectory(void) +{ + static char currentDir[MAX_FILEPATH_LENGTH]; + memset(currentDir, 0, MAX_FILEPATH_LENGTH); + + char *ptr = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); + + return ptr; +} + +// Get filenames in a directory path (max 512 files) +// NOTE: Files count is returned by parameters pointer +char **GetDirectoryFiles(const char *dirPath, int *fileCount) +{ + #define MAX_DIRECTORY_FILES 512 + + ClearDirectoryFiles(); + + // Memory allocation for MAX_DIRECTORY_FILES + dirFilesPath = (char **)RL_MALLOC(sizeof(char *)*MAX_DIRECTORY_FILES); + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); + + int counter = 0; + struct dirent *entity; + DIR *dir = opendir(dirPath); + + if (dir != NULL) // It's a directory + { + // TODO: Reading could be done in two passes, + // first one to count files and second one to read names + // That way we can allocate required memory, instead of a limited pool + + while ((entity = readdir(dir)) != NULL) + { + strcpy(dirFilesPath[counter], entity->d_name); + counter++; + } + + closedir(dir); + } + else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file... + + dirFilesCount = counter; + *fileCount = dirFilesCount; + + return dirFilesPath; +} + +// Clear directory files paths buffers +void ClearDirectoryFiles(void) +{ + if (dirFilesCount > 0) + { + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) RL_FREE(dirFilesPath[i]); + + RL_FREE(dirFilesPath); + } + + dirFilesCount = 0; +} + +// Change working directory, returns true on success +bool ChangeDirectory(const char *dir) +{ + bool result = CHDIR(dir); + + if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir); + + return (result == 0); +} + +// Check if a file has been dropped into window +bool IsFileDropped(void) +{ + if (CORE.Window.dropFilesCount > 0) return true; + else return false; +} + +// Get dropped files names +char **GetDroppedFiles(int *count) +{ + *count = CORE.Window.dropFilesCount; + return CORE.Window.dropFilesPath; +} + +// Clear dropped files paths buffer +void ClearDroppedFiles(void) +{ + if (CORE.Window.dropFilesCount > 0) + { + for (int i = 0; i < CORE.Window.dropFilesCount; i++) RL_FREE(CORE.Window.dropFilesPath[i]); + + RL_FREE(CORE.Window.dropFilesPath); + + CORE.Window.dropFilesCount = 0; + } +} + +// Get file modification time (last write time) +long GetFileModTime(const char *fileName) +{ + struct stat result = { 0 }; + + if (stat(fileName, &result) == 0) + { + time_t mod = result.st_mtime; + + return (long)mod; + } + + return 0; +} + +// Compress data (DEFLATE algorythm) +unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength) +{ + #define COMPRESSION_QUALITY_DEFLATE 8 + + unsigned char *compData = NULL; + +#if defined(SUPPORT_COMPRESSION_API) + // Compress data and generate a valid DEFLATE stream + struct sdefl sdefl = { 0 }; + int bounds = sdefl_bound(dataLength); + compData = (unsigned char *)RL_CALLOC(bounds, 1); + *compDataLength = sdeflate(&sdefl, compData, data, dataLength, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbwi + + TraceLog(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataLength, compDataLength); +#endif + + return compData; +} + +// Decompress data (DEFLATE algorythm) +unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength) +{ + unsigned char *data = NULL; + +#if defined(SUPPORT_COMPRESSION_API) + // Decompress data from a valid DEFLATE stream + data = RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); + int length = sinflate(data, compData, compDataLength); + unsigned char *temp = RL_REALLOC(data, length); + + if (temp != NULL) data = temp; + else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory"); + + *dataLength = length; + + TraceLog(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataLength, dataLength); +#endif + + return data; +} + +// Save integer value to storage file (to defined position) +// NOTE: Storage positions is directly related to file memory layout (4 bytes each integer) +bool SaveStorageValue(unsigned int position, int value) +{ + bool success = false; + +#if defined(SUPPORT_DATA_STORAGE) + char path[512] = { 0 }; +#if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#elif defined(PLATFORM_UWP) + strcpy(path, CORE.UWP.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#else + strcpy(path, STORAGE_DATA_FILE); +#endif + + unsigned int dataSize = 0; + unsigned int newDataSize = 0; + unsigned char *fileData = LoadFileData(path, &dataSize); + unsigned char *newFileData = NULL; + + if (fileData != NULL) + { + if (dataSize <= (position*sizeof(int))) + { + // Increase data size up to position and store value + newDataSize = (position + 1)*sizeof(int); + newFileData = (unsigned char *)RL_REALLOC(fileData, newDataSize); + + if (newFileData != NULL) + { + // RL_REALLOC succeded + int *dataPtr = (int *)newFileData; + dataPtr[position] = value; + } + else + { + // RL_REALLOC failed + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size", path, dataSize, position*sizeof(int)); + + // We store the old size of the file + newFileData = fileData; + newDataSize = dataSize; + } + } + else + { + // Store the old size of the file + newFileData = fileData; + newDataSize = dataSize; + + // Replace value on selected position + int *dataPtr = (int *)newFileData; + dataPtr[position] = value; + } + + success = SaveFileData(path, newFileData, newDataSize); + RL_FREE(newFileData); + } + else + { + TRACELOG(LOG_INFO, "FILEIO: [%s] File not found, creating it", path); + + dataSize = (position + 1)*sizeof(int); + fileData = (unsigned char *)RL_MALLOC(dataSize); + int *dataPtr = (int *)fileData; + dataPtr[position] = value; + + success = SaveFileData(path, fileData, dataSize); + UnloadFileData(fileData); + } +#endif + + return success; +} + +// Load integer value from storage file (from defined position) +// NOTE: If requested position could not be found, value 0 is returned +int LoadStorageValue(unsigned int position) +{ + int value = 0; + +#if defined(SUPPORT_DATA_STORAGE) + char path[512] = { 0 }; +#if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#elif defined(PLATFORM_UWP) + strcpy(path, CORE.UWP.internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_DATA_FILE); +#else + strcpy(path, STORAGE_DATA_FILE); +#endif + + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(path, &dataSize); + + if (fileData != NULL) + { + if (dataSize < (position*4)) TRACELOG(LOG_WARNING, "SYSTEM: Failed to find storage position"); + else + { + int *dataPtr = (int *)fileData; + value = dataPtr[position]; + } + + UnloadFileData(fileData); + } +#endif + return value; +} + +// Open URL with default system browser (if available) +// NOTE: This function is only safe to use if you control the URL given. +// A user could craft a malicious string performing another action. +// Only call this function yourself not with user input or make sure to check the string yourself. +// Ref: https://github.com/raysan5/raylib/issues/686 +void OpenURL(const char *url) +{ + // Small security check trying to avoid (partially) malicious code... + // sorry for the inconvenience when you hit this point... + if (strchr(url, '\'') != NULL) + { + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL is not valid"); + } + else + { +#if defined(PLATFORM_DESKTOP) + char *cmd = (char *)RL_CALLOC(strlen(url) + 10, sizeof(char)); + #if defined(_WIN32) + sprintf(cmd, "explorer %s", url); + #endif + #if defined(__linux__) || defined(__FreeBSD__) + sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser + #endif + #if defined(__APPLE__) + sprintf(cmd, "open '%s'", url); + #endif + system(cmd); + RL_FREE(cmd); +#endif +#if defined(PLATFORM_WEB) + emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); +#endif + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions +//---------------------------------------------------------------------------------- +// Detect if a key has been pressed once +bool IsKeyPressed(int key) +{ + bool pressed = false; + + if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true; + else pressed = false; + + return pressed; +} + +// Detect if a key is being pressed (key held down) +bool IsKeyDown(int key) +{ + if (CORE.Input.Keyboard.currentKeyState[key] == 1) return true; + else return false; +} + +// Detect if a key has been released once +bool IsKeyReleased(int key) +{ + bool released = false; + + if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true; + else released = false; + + return released; +} + +// Detect if a key is NOT being pressed (key not held down) +bool IsKeyUp(int key) +{ + if (CORE.Input.Keyboard.currentKeyState[key] == 0) return true; + else return false; +} + +// Get the last key pressed +int GetKeyPressed(void) +{ + int value = 0; + + if (CORE.Input.Keyboard.keyPressedQueueCount > 0) + { + // Get character from the queue head + value = CORE.Input.Keyboard.keyPressedQueue[0]; + + // Shift elements 1 step toward the head. + for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) + CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; + + // Reset last character in the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 0; + CORE.Input.Keyboard.keyPressedQueueCount--; + } + + return value; +} + +// Get the last char pressed +int GetCharPressed(void) +{ + int value = 0; + + if (CORE.Input.Keyboard.charPressedQueueCount > 0) + { + // Get character from the queue head + value = CORE.Input.Keyboard.charPressedQueue[0]; + + // Shift elements 1 step toward the head. + for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++) + CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1]; + + // Reset last character in the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = 0; + CORE.Input.Keyboard.charPressedQueueCount--; + } + + return value; +} + +// Set a custom key to exit program +// NOTE: default exitKey is ESCAPE +void SetExitKey(int key) +{ +#if !defined(PLATFORM_ANDROID) + CORE.Input.Keyboard.exitKey = key; +#endif +} + +// NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB) + +// Detect if a gamepad is available +bool IsGamepadAvailable(int gamepad) +{ + bool result = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; + + return result; +} + +// Check gamepad name (if available) +bool IsGamepadName(int gamepad, const char *name) +{ + bool result = false; + const char *currentName = NULL; + + if (CORE.Input.Gamepad.ready[gamepad]) currentName = GetGamepadName(gamepad); + if ((name != NULL) && (currentName != NULL)) result = (strcmp(name, currentName) == 0); + + return result; +} + +// Return gamepad internal name id +const char *GetGamepadName(int gamepad) +{ +#if defined(PLATFORM_DESKTOP) + if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad); + else return NULL; +#endif +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name); + return CORE.Input.Gamepad.name; +#endif + return NULL; +} + +// Return gamepad axis count +int GetGamepadAxisCount(int gamepad) +{ +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + int axisCount = 0; + if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount); + CORE.Input.Gamepad.axisCount = axisCount; +#endif + + return CORE.Input.Gamepad.axisCount; +} + +// Return axis movement vector for a gamepad +float GetGamepadAxisMovement(int gamepad, int axis) +{ + float value = 0; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) && + (fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) > 0.1f)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA + + return value; +} + +// Detect if a gamepad button has been pressed once +bool IsGamepadButtonPressed(int gamepad, int button) +{ + bool pressed = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.previousState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) pressed = true; + else pressed = false; + + return pressed; +} + +// Detect if a gamepad button is being pressed +bool IsGamepadButtonDown(int gamepad, int button) +{ + bool result = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) result = true; + + return result; +} + +// Detect if a gamepad button has NOT been pressed once +bool IsGamepadButtonReleased(int gamepad, int button) +{ + bool released = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.previousState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) released = true; + else released = false; + + return released; +} + +// Detect if a gamepad button is NOT being pressed +bool IsGamepadButtonUp(int gamepad, int button) +{ + bool result = false; + + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) result = true; + + return result; +} + +// Get the last gamepad button pressed +int GetGamepadButtonPressed(void) +{ + return CORE.Input.Gamepad.lastButtonPressed; +} + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + int result = 0; + +#if defined(PLATFORM_DESKTOP) + result = glfwUpdateGamepadMappings(mappings); +#endif + + return result; +} + +// Detect if a mouse button has been pressed once +bool IsMouseButtonPressed(int button) +{ + bool pressed = false; + + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; + + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; + + return pressed; +} + +// Detect if a mouse button is being pressed +bool IsMouseButtonDown(int button) +{ + bool down = false; + + if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; + + // Map touches to mouse buttons checking + if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; + + return down; +} + +// Detect if a mouse button has been released once +bool IsMouseButtonReleased(int button) +{ + bool released = false; + + if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; + + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; + + return released; +} + +// Detect if a mouse button is NOT being pressed +bool IsMouseButtonUp(int button) +{ + return !IsMouseButtonDown(button); +} + +// Returns mouse position X +int GetMouseX(void) +{ +#if defined(PLATFORM_ANDROID) + return (int)CORE.Input.Touch.position[0].x; +#else + return (int)((CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); +#endif +} + +// Returns mouse position Y +int GetMouseY(void) +{ +#if defined(PLATFORM_ANDROID) + return (int)CORE.Input.Touch.position[0].y; +#else + return (int)((CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); +#endif +} + +// Returns mouse position XY +Vector2 GetMousePosition(void) +{ + Vector2 position = { 0 }; + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) + position = GetTouchPosition(0); +#else + position.x = (CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x; + position.y = (CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y; +#endif + + return position; +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.position = (Vector2){ (float)x, (float)y }; +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // NOTE: emscripten not implemented + glfwSetCursorPos(CORE.Window.handle, CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y); +#endif +#if defined(PLATFORM_UWP) + UWPGetMouseSetPosFunc()(x, y); +#endif +} + +// Set mouse offset +// NOTE: Useful when rendering to different size targets +void SetMouseOffset(int offsetX, int offsetY) +{ + CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY }; +} + +// Set mouse scaling +// NOTE: Useful when rendering to different size targets +void SetMouseScale(float scaleX, float scaleY) +{ + CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY }; +} + +// Returns mouse wheel movement Y +float GetMouseWheelMove(void) +{ +#if defined(PLATFORM_ANDROID) + return 0.0f; +#endif +#if defined(PLATFORM_WEB) + return CORE.Input.Mouse.previousWheelMove/100.0f; +#endif + + return CORE.Input.Mouse.previousWheelMove; +} + +// Set mouse cursor +// NOTE: This is a no-op on platforms other than PLATFORM_DESKTOP +void SetMouseCursor(int cursor) +{ +#if defined(PLATFORM_DESKTOP) + CORE.Input.Mouse.cursor = cursor; + if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(CORE.Window.handle, NULL); + else + { + // NOTE: We are relating internal GLFW enum values to our MouseCursor enum values + glfwSetCursor(CORE.Window.handle, glfwCreateStandardCursor(0x00036000 + cursor)); + } +#endif +} + +// Returns touch position X for touch point 0 (relative to screen size) +int GetTouchX(void) +{ +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return (int)CORE.Input.Touch.position[0].x; +#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM + return GetMouseX(); +#endif +} + +// Returns touch position Y for touch point 0 (relative to screen size) +int GetTouchY(void) +{ +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) + return (int)CORE.Input.Touch.position[0].y; +#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM + return GetMouseY(); +#endif +} + +// Returns touch position XY for a touch point index (relative to screen size) +// TODO: Touch position should be scaled depending on display size and render size +Vector2 GetTouchPosition(int index) +{ + Vector2 position = { -1.0f, -1.0f }; + +#if defined(PLATFORM_DESKTOP) + // TODO: GLFW does not support multi-touch input just yet + // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch + // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages + if (index == 0) position = GetMousePosition(); +#endif +#if defined(PLATFORM_ANDROID) + if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; + else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); + + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + position.x = position.x*((float)CORE.Window.screen.width/(float)(CORE.Window.display.width - CORE.Window.renderOffset.x)) - CORE.Window.renderOffset.x/2; + position.y = position.y*((float)CORE.Window.screen.height/(float)(CORE.Window.display.height - CORE.Window.renderOffset.y)) - CORE.Window.renderOffset.y/2; + } + else + { + position.x = position.x*((float)CORE.Window.render.width/(float)CORE.Window.display.width) - CORE.Window.renderOffset.x/2; + position.y = position.y*((float)CORE.Window.render.height/(float)CORE.Window.display.height) - CORE.Window.renderOffset.y/2; + } +#endif +#if defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; + else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); + + // TODO: Touch position scaling required? +#endif + + return position; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize display device and framebuffer +// NOTE: width and height represent the screen (framebuffer) desired size, not actual display size +// If width or height are 0, default display size will be used for framebuffer size +// NOTE: returns false in case graphic device could not be created +static bool InitGraphicsDevice(int width, int height) +{ + CORE.Window.screen.width = width; // User desired width + CORE.Window.screen.height = height; // User desired height + CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default + + // NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars... + // ...in top-down or left-right to match display aspect ratio (no weird scalings) + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + glfwSetErrorCallback(ErrorCallback); + +#if defined(__APPLE__) + glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); +#endif + + if (!glfwInit()) + { + TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW"); + return false; + } + + // NOTE: Getting video modes is not implemented in emscripten GLFW3 version +#if defined(PLATFORM_DESKTOP) + // Find monitor resolution + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + if (!monitor) + { + TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); + return false; + } + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + + // Screen size security check + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; +#endif // PLATFORM_DESKTOP + +#if defined(PLATFORM_WEB) + CORE.Window.display.width = CORE.Window.screen.width; + CORE.Window.display.height = CORE.Window.screen.height; +#endif // PLATFORM_WEB + + glfwDefaultWindowHints(); // Set default windows hints + //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits + //glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits + //glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits + //glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits + //glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits + //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window + //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API + //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers + + // Check window creation flags + if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; + + if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window + else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden + + if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window + else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window + + if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window + else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable + + // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization + if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + + // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization + if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + + if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); + else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); + + if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); + + // NOTE: Some GLFW flags are not supported on HTML5 +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer + else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer + + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // Resize window content area based on the monitor content scale. + // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11. + // On platforms like macOS the resolution of the framebuffer is changed independently of the window size. + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on + #if defined(__APPLE__) + glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); + #endif + } + else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); +#endif + + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 + } + + // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version + // with forward compatibility to older OpenGL versions. + // For example, if using OpenGL 1.1, driver can provide a 4.3 context forward compatible. + + // Check selection OpenGL version + if (rlGetVersion() == OPENGL_21) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) + } + else if (rlGetVersion() == OPENGL_33) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! + // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE +#if defined(__APPLE__) + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires fordward compatibility +#else + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! +#endif + //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context + } + else if (rlGetVersion() == OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); +#if defined(PLATFORM_DESKTOP) + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); +#else + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); +#endif + } + +#if defined(PLATFORM_DESKTOP) + // NOTE: GLFW 3.4+ defers initialization of the Joystick subsystem on the first call to any Joystick related functions. + // Forcing this initialization here avoids doing it on `PollInputEvents` called by `EndDrawing` after first frame has been just drawn. + // The initialization will still happen and possible delays still occur, but before the window is shown, which is a nicer experience. + // REF: https://github.com/raysan5/raylib/issues/1554 + if (MAX_GAMEPADS > 0) glfwSetJoystickCallback(NULL); +#endif + + if (CORE.Window.fullscreen) + { + // remember center for switchinging from fullscreen to window + CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2; + CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2; + + if (CORE.Window.position.x < 0) CORE.Window.position.x = 0; + if (CORE.Window.position.y < 0) CORE.Window.position.y = 0; + + // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); + + // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height + for (int i = 0; i < count; i++) + { + if ((unsigned int)modes[i].width >= CORE.Window.screen.width) + { + if ((unsigned int)modes[i].height >= CORE.Window.screen.height) + { + CORE.Window.display.width = modes[i].width; + CORE.Window.display.height = modes[i].height; + break; + } + } + } + +#if defined(PLATFORM_DESKTOP) + // If we are windowed fullscreen, ensures that window does not minimize when focus is lost + if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) + { + glfwWindowHint(GLFW_AUTO_ICONIFY, 0); + } +#endif + TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + + // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, + // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), + // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched + // by the sides to fit all monitor space... + + // Try to setup the most appropiate fullscreen framebuffer for the requested screenWidth/screenHeight + // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) + // Modified global variables: CORE.Window.screen.width/CORE.Window.screen.height - CORE.Window.render.width/CORE.Window.render.height - CORE.Window.renderOffset.x/CORE.Window.renderOffset.y - CORE.Window.screenScale + // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... + // HighDPI monitors are properly considered in a following similar function: SetupViewport() + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + CORE.Window.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL); + + // NOTE: Full-screen change, not working properly... + //glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + else + { + // No-fullscreen window creation + CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); + + if (CORE.Window.handle) + { +#if defined(PLATFORM_DESKTOP) + // Center window on screen + int windowPosX = CORE.Window.display.width/2 - CORE.Window.screen.width/2; + int windowPosY = CORE.Window.display.height/2 - CORE.Window.screen.height/2; + + if (windowPosX < 0) windowPosX = 0; + if (windowPosY < 0) windowPosY = 0; + + glfwSetWindowPos(CORE.Window.handle, windowPosX, windowPosY); +#endif + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + } + } + + if (!CORE.Window.handle) + { + glfwTerminate(); + TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); + return false; + } + else + { + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); +#if defined(PLATFORM_DESKTOP) + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); +#endif + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } + + // Set window callback events + glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! +#if !defined(PLATFORM_WEB) + glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback); +#endif + glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback); + glfwSetWindowFocusCallback(CORE.Window.handle, WindowFocusCallback); + glfwSetDropCallback(CORE.Window.handle, WindowDropCallback); + // Set input callback events + glfwSetKeyCallback(CORE.Window.handle, KeyCallback); + glfwSetCharCallback(CORE.Window.handle, CharCallback); + glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback); + glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes + glfwSetScrollCallback(CORE.Window.handle, MouseScrollCallback); + glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback); + + glfwMakeContextCurrent(CORE.Window.handle); + +#if !defined(PLATFORM_WEB) + glfwSwapInterval(0); // No V-Sync by default +#endif + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration + if (CORE.Window.flags & FLAG_VSYNC_HINT) + { + // WARNING: It seems to hits a critical render path in Intel HD Graphics + glfwSwapInterval(1); + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC"); + } +#endif // PLATFORM_DESKTOP || PLATFORM_WEB + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + CORE.Window.fullscreen = true; + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + +#if defined(PLATFORM_RPI) + bcm_host_init(); + + DISPMANX_ELEMENT_HANDLE_T dispmanElement; + DISPMANX_DISPLAY_HANDLE_T dispmanDisplay; + DISPMANX_UPDATE_HANDLE_T dispmanUpdate; + + VC_RECT_T dstRect; + VC_RECT_T srcRect; +#endif + +#if defined(PLATFORM_DRM) + CORE.Window.fd = -1; + CORE.Window.connector = NULL; + CORE.Window.modeIndex = -1; + CORE.Window.crtc = NULL; + CORE.Window.gbmDevice = NULL; + CORE.Window.gbmSurface = NULL; + CORE.Window.prevBO = NULL; + CORE.Window.prevFB = 0; + +#if defined(DEFAULT_GRAPHIC_DEVICE_DRM) + CORE.Window.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR); +#else + TRACELOG(LOG_INFO, "DISPLAY: No graphic card set, trying card1"); + CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // VideoCore VI (Raspberry Pi 4) + if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) + { + TRACELOG(LOG_INFO, "DISPLAY: Failed to open graphic card1, trying card0"); + CORE.Window.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3) + } +#endif + if (-1 == CORE.Window.fd) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card"); + return false; + } + + drmModeRes *res = drmModeGetResources(CORE.Window.fd); + if (!res) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources"); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors); + for (size_t i = 0; i < res->count_connectors; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i); + drmModeConnector *con = drmModeGetConnector(CORE.Window.fd, res->connectors[i]); + TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes); + if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected"); + CORE.Window.connector = con; + break; + } + else + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode NOT connected (deleting)"); + drmModeFreeConnector(con); + } + } + if (!CORE.Window.connector) + { + TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found"); + drmModeFreeResources(res); + return false; + } + + drmModeEncoder *enc = drmModeGetEncoder(CORE.Window.fd, CORE.Window.connector->encoder_id); + if (!enc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder"); + drmModeFreeResources(res); + return false; + } + + CORE.Window.crtc = drmModeGetCrtc(CORE.Window.fd, enc->crtc_id); + if (!CORE.Window.crtc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + // If InitWindow should use the current mode find it in the connector's mode list + if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0)) + { + TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode..."); + + CORE.Window.modeIndex = FindMatchingConnectorMode(CORE.Window.connector, &CORE.Window.crtc->mode); + + if (CORE.Window.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT; + const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60; + // try to find an exact matching mode + CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + // if nothing found, try to find a nearly matching mode + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + // if nothing found, try to find an exactly matching mode including interlaced + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + // if nothing found, try to find a nearly matching mode including interlaced + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + // if nothing found, there is no suitable mode + if (CORE.Window.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + CORE.Window.display.width = CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay; + CORE.Window.display.height = CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay; + + TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", CORE.Window.connector->modes[CORE.Window.modeIndex].name, + CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, + (CORE.Window.connector->modes[CORE.Window.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', + CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh); + + // Use the width and height of the surface for render + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + + drmModeFreeEncoder(enc); + enc = NULL; + + drmModeFreeResources(res); + res = NULL; + + CORE.Window.gbmDevice = gbm_create_device(CORE.Window.fd); + if (!CORE.Window.gbmDevice) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device"); + return false; + } + + CORE.Window.gbmSurface = gbm_surface_create(CORE.Window.gbmDevice, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, + CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!CORE.Window.gbmSurface) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface"); + return false; + } +#endif + + EGLint samples = 0; + EGLint sampleBuffer = 0; + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + samples = 4; + sampleBuffer = 1; + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + } + + const EGLint framebufferAttribs[] = + { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? +#if defined(PLATFORM_DRM) + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! +#endif + EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) + EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) + EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) +#if defined(PLATFORM_DRM) + EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) +#endif + //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) + EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) + //EGL_STENCIL_SIZE, 8, // Stencil buffer size + EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA + EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) + EGL_NONE + }; + + const EGLint contextAttribs[] = + { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + +#if defined(PLATFORM_UWP) + const EGLint surfaceAttributes[] = + { + // EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER is part of the same optimization as EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER (see above). + // If you have compilation issues with it then please update your Visual Studio templates. + EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER, EGL_TRUE, + EGL_NONE + }; + + const EGLint defaultDisplayAttributes[] = + { + // These are the default display attributes, used to request ANGLE's D3D11 renderer. + // eglInitialize will only succeed with these attributes if the hardware supports D3D11 Feature Level 10_0+. + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + + // EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER is an optimization that can have large performance benefits on mobile devices. + // Its syntax is subject to change, though. Please update your Visual Studio templates if you experience compilation issues with it. + EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, + + // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that enables ANGLE to automatically call + // the IDXGIDevice3::Trim method on behalf of the application when it gets suspended. + // Calling IDXGIDevice3::Trim when an application is suspended is a Windows Store application certification requirement. + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, + EGL_NONE, + }; + + const EGLint fl9_3DisplayAttributes[] = + { + // These can be used to request ANGLE's D3D11 renderer, with D3D11 Feature Level 9_3. + // These attributes are used if the call to eglInitialize fails with the default display attributes. + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, 9, + EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, 3, + EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, + EGL_NONE, + }; + + const EGLint warpDisplayAttributes[] = + { + // These attributes can be used to request D3D11 WARP. + // They are used if eglInitialize fails with both the default display attributes and the 9_3 display attributes. + EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE, + EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, + EGL_NONE, + }; + + // eglGetPlatformDisplayEXT is an alternative to eglGetDisplay. It allows us to pass in display attributes, used to configure D3D11. + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)(eglGetProcAddress("eglGetPlatformDisplayEXT")); + if (!eglGetPlatformDisplayEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get function pointer: eglGetPlatformDisplayEXT()"); + return false; + } + + // + // To initialize the display, we make three sets of calls to eglGetPlatformDisplayEXT and eglInitialize, with varying + // parameters passed to eglGetPlatformDisplayEXT: + // 1) The first calls uses "defaultDisplayAttributes" as a parameter. This corresponds to D3D11 Feature Level 10_0+. + // 2) If eglInitialize fails for step 1 (e.g. because 10_0+ isn't supported by the default GPU), then we try again + // using "fl9_3DisplayAttributes". This corresponds to D3D11 Feature Level 9_3. + // 3) If eglInitialize fails for step 2 (e.g. because 9_3+ isn't supported by the default GPU), then we try again + // using "warpDisplayAttributes". This corresponds to D3D11 Feature Level 11_0 on WARP, a D3D11 software rasterizer. + // + + // This tries to initialize EGL to D3D11 Feature Level 10_0+. See above comment for details. + CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes); + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // This tries to initialize EGL to D3D11 Feature Level 9_3, if 10_0+ is unavailable (e.g. on some mobile devices). + CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, fl9_3DisplayAttributes); + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // This initializes EGL to D3D11 Feature Level 11_0 on WARP, if 9_3+ is unavailable on the default GPU. + CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, warpDisplayAttributes); + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + } + } + + EGLint numConfigs = 0; + if ((eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose first EGL configuration"); + return false; + } + + // Create a PropertySet and initialize with the EGLNativeWindowType. + //PropertySet^ surfaceCreationProperties = ref new PropertySet(); + //surfaceCreationProperties->Insert(ref new String(EGLNativeWindowTypeProperty), window); // CoreWindow^ window + + // You can configure the surface to render at a lower resolution and be scaled up to + // the full window size. The scaling is often free on mobile hardware. + // + // One way to configure the SwapChainPanel is to specify precisely which resolution it should render at. + // Size customRenderSurfaceSize = Size(800, 600); + // surfaceCreationProperties->Insert(ref new String(EGLRenderSurfaceSizeProperty), PropertyValue::CreateSize(customRenderSurfaceSize)); + // + // Another way is to tell the SwapChainPanel to render at a certain scale factor compared to its size. + // e.g. if the SwapChainPanel is 1920x1280 then setting a factor of 0.5f will make the app render at 960x640 + // float customResolutionScale = 0.5f; + // surfaceCreationProperties->Insert(ref new String(EGLRenderResolutionScaleProperty), PropertyValue::CreateSingle(customResolutionScale)); + + + // eglCreateWindowSurface() requires a EGLNativeWindowType parameter, + // In Windows platform: typedef HWND EGLNativeWindowType; + + + // Property: EGLNativeWindowTypeProperty + // Type: IInspectable + // Description: Set this property to specify the window type to use for creating a surface. + // If this property is missing, surface creation will fail. + // + //const wchar_t EGLNativeWindowTypeProperty[] = L"EGLNativeWindowTypeProperty"; + + //https://stackoverflow.com/questions/46550182/how-to-create-eglsurface-using-c-winrt-and-angle + + //CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, reinterpret_cast(surfaceCreationProperties), surfaceAttributes); + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType) UWPGetCoreWindowPtr(), surfaceAttributes); + if (CORE.Window.surface == EGL_NO_SURFACE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL fullscreen surface"); + return false; + } + + CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); + if (CORE.Window.context == EGL_NO_CONTEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); + return false; + } + + // Get EGL device window size + eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_WIDTH, &CORE.Window.screen.width); + eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_HEIGHT, &CORE.Window.screen.height); + + // Get display size + UWPGetDisplaySizeFunc()(&CORE.Window.display.width, &CORE.Window.display.height); + + // Use the width and height of the surface for render + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + +#endif // PLATFORM_UWP + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + EGLint numConfigs = 0; + + // Get an EGL device connection +#if defined(PLATFORM_DRM) + CORE.Window.device = eglGetDisplay((EGLNativeDisplayType)CORE.Window.gbmDevice); +#else + CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); +#endif + if (CORE.Window.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + + // Initialize the EGL device connection + if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) + { + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return false; + } + +#if defined(PLATFORM_DRM) + if (!eglChooseConfig(CORE.Window.device, NULL, NULL, 0, &numConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError()); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs); + + EGLConfig *configs = RL_CALLOC(numConfigs, sizeof(*configs)); + if (!configs) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs"); + return false; + } + + EGLint matchingNumConfigs = 0; + if (!eglChooseConfig(CORE.Window.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError()); + free(configs); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs); + + // find the EGL config that matches the previously setup GBM format + int found = 0; + for (EGLint i = 0; i < matchingNumConfigs; ++i) + { + EGLint id = 0; + if (!eglGetConfigAttrib(CORE.Window.device, configs[i], EGL_NATIVE_VISUAL_ID, &id)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError()); + continue; + } + + if (GBM_FORMAT_ARGB8888 == id) + { + TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i); + CORE.Window.config = configs[i]; + found = 1; + break; + } + } + + RL_FREE(configs); + + if (!found) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config"); + return false; + } +#else + // Get an appropriate EGL framebuffer configuration + eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs); +#endif + + // Set rendering API + eglBindAPI(EGL_OPENGL_ES_API); + + // Create an EGL rendering context + CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); + if (CORE.Window.context == EGL_NO_CONTEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); + return false; + } +#endif + + // Create an EGL window surface + //--------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) + EGLint displayFormat = 0; + + // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() + // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID + eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); + + // At this point we need to manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + ANativeWindow_setBuffersGeometry(CORE.Android.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); + //ANativeWindow_setBuffersGeometry(CORE.Android.app->window, 0, 0, displayFormat); // Force use of native display size + + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, CORE.Android.app->window, NULL); +#endif // PLATFORM_ANDROID + +#if defined(PLATFORM_RPI) + graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height); + + // Screen size security check + if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; + + // At this point we need to manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + dstRect.x = 0; + dstRect.y = 0; + dstRect.width = CORE.Window.display.width; + dstRect.height = CORE.Window.display.height; + + srcRect.x = 0; + srcRect.y = 0; + srcRect.width = CORE.Window.render.width << 16; + srcRect.height = CORE.Window.render.height << 16; + + // NOTE: RPI dispmanx windowing system takes care of source rectangle scaling to destination rectangle by hardware (no cost) + // Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio + + VC_DISPMANX_ALPHA_T alpha; + alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; + //alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE; // TODO: Allow transparent framebuffer! -> FLAG_WINDOW_TRANSPARENT + alpha.opacity = 255; // Set transparency level for framebuffer, requires EGLAttrib: EGL_TRANSPARENT_TYPE + alpha.mask = 0; + + dispmanDisplay = vc_dispmanx_display_open(0); // LCD + dispmanUpdate = vc_dispmanx_update_start(0); + + dispmanElement = vc_dispmanx_element_add(dispmanUpdate, dispmanDisplay, 0/*layer*/, &dstRect, 0/*src*/, + &srcRect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE); + + CORE.Window.handle.element = dispmanElement; + CORE.Window.handle.width = CORE.Window.render.width; + CORE.Window.handle.height = CORE.Window.render.height; + vc_dispmanx_update_submit_sync(dispmanUpdate); + + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL); + + const unsigned char *const renderer = glGetString(GL_RENDERER); + if (renderer) TRACELOG(LOG_INFO, "DISPLAY: Renderer name is: %s", renderer); + else TRACELOG(LOG_WARNING, "DISPLAY: Failed to get renderer name"); + //--------------------------------------------------------------------------------- +#endif // PLATFORM_RPI + +#if defined(PLATFORM_DRM) + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL); + if (EGL_NO_SURFACE == CORE.Window.surface) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError()); + return false; + } + + // At this point we need to manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); +#endif // PLATFORM_DRM + + // There must be at least one frame displayed before the buffers are swapped + //eglSwapInterval(CORE.Window.device, 1); + + if (eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context) == EGL_FALSE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface"); + return false; + } + else + { + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } +#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + rlLoadExtensions(glfwGetProcAddress); +#else + rlLoadExtensions(eglGetProcAddress); +#endif + + // Initialize OpenGL context (states and resources) + // NOTE: CORE.Window.screen.width and CORE.Window.screen.height not used, just stored as globals in rlgl + rlglInit(CORE.Window.screen.width, CORE.Window.screen.height); + + int fbWidth = CORE.Window.render.width; + int fbHeight = CORE.Window.render.height; + +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling + // Framebuffer scaling should be activated with: glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); + #if !defined(__APPLE__) + glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight); + + // Screen scaling matrix is required in case desired screen area is different than display area + CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); + + // Mouse input scaling for the new screen size + SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); + #endif + } +#endif + + // Setup default viewport + SetupViewport(fbWidth, fbHeight); + + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; + + ClearBackground(RAYWHITE); // Default background color for raylib games :P + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_UWP) + CORE.Window.ready = true; +#endif + + if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); + + return true; +} + +// Set viewport for a provided width and height +static void SetupViewport(int width, int height) +{ + CORE.Window.render.width = width; + CORE.Window.render.height = height; + + // Set viewport width and height + // NOTE: We consider render size (scaled) and offset in case black bars are required and + // render area does not match full display area (this situation is only applicable on fullscreen mode) +#if defined(__APPLE__) + float xScale = 1.0f, yScale = 1.0f; + glfwGetWindowContentScale(CORE.Window.handle, &xScale, &yScale); + rlViewport(CORE.Window.renderOffset.x/2*xScale, CORE.Window.renderOffset.y/2*yScale, (CORE.Window.render.width - CORE.Window.renderOffset.x)*xScale, (CORE.Window.render.height - CORE.Window.renderOffset.y)*yScale); +#else + rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width - CORE.Window.renderOffset.x, CORE.Window.render.height - CORE.Window.renderOffset.y); +#endif + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlLoadIdentity(); // Reset current matrix (projection) + + // Set orthographic projection to current framebuffer size + // NOTE: Configured top-left corner as (0, 0) + rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) +} + +// Compute framebuffer size relative to screen size and display size +// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified +static void SetupFramebuffer(int width, int height) +{ + // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Downscaling required: Screen size (%ix%i) is bigger than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + // Downscaling to fit display with border-bars + float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; + float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; + + if (widthRatio <= heightRatio) + { + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); + CORE.Window.render.height = CORE.Window.display.height; + CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); + CORE.Window.renderOffset.y = 0; + } + + // Screen scaling required + float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; + CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); + + // NOTE: We render to full display resolution! + // We just need to calculate above parameters for downscale matrix and offsets + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = CORE.Window.display.height; + + TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); + } + else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) + { + // Required screen size is smaller than display size + TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + // Upscaling to fit display with border-bars + float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; + float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + + if (displayRatio <= screenRatio) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); + CORE.Window.renderOffset.y = 0; + } + } + else + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = 0; + } +} + +// Initialize hi-resolution timer +static void InitTimer(void) +{ + srand((unsigned int)time(NULL)); // Initialize random seed + +// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. +// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. +// High resolutions can also prevent the CPU power management system from entering power-saving modes. +// Setting a higher resolution does not improve the accuracy of the high-resolution performance counter. +#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) + timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + struct timespec now = { 0 }; + + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + { + CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; + } + else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); +#endif + + CORE.Time.previous = GetTime(); // Get time as double +} + +// Wait for some milliseconds (stop program execution) +// NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could +// take longer than expected... for that reason we use the busy wait loop +// Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected +// Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timming on Win32! +static void Wait(float ms) +{ +#if defined(PLATFORM_UWP) + UWPGetSleepFunc()(ms/1000); + return; +#endif + +#if defined(SUPPORT_BUSY_WAIT_LOOP) + double prevTime = GetTime(); + double nextTime = 0.0; + + // Busy wait loop + while ((nextTime - prevTime) < ms/1000.0f) nextTime = GetTime(); +#else + #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) + #define MAX_HALFBUSY_WAIT_TIME 4 + double destTime = GetTime() + ms/1000; + if (ms > MAX_HALFBUSY_WAIT_TIME) ms -= MAX_HALFBUSY_WAIT_TIME; + #endif + + #if defined(_WIN32) + Sleep((unsigned int)ms); + #endif + #if defined(__linux__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) + struct timespec req = { 0 }; + time_t sec = (int)(ms/1000.0f); + ms -= (sec*1000); + req.tv_sec = sec; + req.tv_nsec = ms*1000000L; + + // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. + while (nanosleep(&req, &req) == -1) continue; + #endif + #if defined(__APPLE__) + usleep(ms*1000.0f); + #endif + + #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) + while (GetTime() < destTime) { } + #endif +#endif +} + +// Poll (store) all input events +static void PollInputEvents(void) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is just called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + +#if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = -1; + CORE.Input.Gamepad.axisCount = 0; +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + // Register previous keys states + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + PollKeyboardEvents(); + + // Register previous mouse states + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = 0.0f; + for (int i = 0; i < 3; i++) + { + CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i]; + } + + // Register gamepads buttons events + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + } + } +#endif + +#if defined(PLATFORM_UWP) + // Register previous keys states + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) + { + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + } + } + + // Register previous mouse states + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = 0.0f; + + for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; +#endif // PLATFORM_UWP + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) + + // Register previous keys states + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + // Register previous mouse states + for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Register previous mouse wheel state + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = 0.0f; +#endif + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + +#if defined(PLATFORM_DESKTOP) + // Check if gamepads are ready + // NOTE: We do it here in case of disconnection + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true; + else CORE.Input.Gamepad.ready[i] = false; + } + + // Register gamepads buttons events + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + + // Get current gamepad state + // NOTE: There is no callback available, so we get it manually + // Get remapped buttons + GLFWgamepadstate state = { 0 }; + glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller + + const unsigned char *buttons = state.buttons; + + for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++) + { + GamepadButton button = -1; + + switch (k) + { + case GLFW_GAMEPAD_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + + case GLFW_GAMEPAD_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case GLFW_GAMEPAD_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; + case GLFW_GAMEPAD_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + + case GLFW_GAMEPAD_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + default: break; + } + + if (button != -1) // Check for valid button + { + if (buttons[k] == GLFW_PRESS) + { + CORE.Input.Gamepad.currentState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentState[i][button] = 0; + } + } + + // Get current axis state + const float *axes = state.axes; + + for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++) + { + CORE.Input.Gamepad.axisState[i][k] = axes[k]; + } + + // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) + CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); + CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); + + CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST + 1; + } + } + + CORE.Window.resizedLastFrame = false; + +#if defined(SUPPORT_EVENTS_WAITING) + glfwWaitEvents(); +#else + glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events! +#endif +#endif // PLATFORM_DESKTOP + +// Gamepad support using emscripten API +// NOTE: GLFW3 joystick functionality not available in web +#if defined(PLATFORM_WEB) + // Get number of gamepads connected + int numGamepads = 0; + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); + + for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) + { + // Register previous gamepad button states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + + EmscriptenGamepadEvent gamepadState; + + int result = emscripten_get_gamepad_status(i, &gamepadState); + + if (result == EMSCRIPTEN_RESULT_SUCCESS) + { + // Register buttons data for every connected gamepad + for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) + { + GamepadButton button = -1; + + // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface + switch (j) + { + case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; + case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; + case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + default: break; + } + + if (button != -1) // Check for valid button + { + if (gamepadState.digitalButton[j] == 1) + { + CORE.Input.Gamepad.currentState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentState[i][button] = 0; + } + + //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + } + + // Register axis data for every connected gamepad + for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++) + { + CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; + } + + CORE.Input.Gamepad.axisCount = gamepadState.numAxes; + } + } +#endif + +#if defined(PLATFORM_ANDROID) + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < 260; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + + // Android ALooper_pollAll() variables + int pollResult = 0; + int pollEvents = 0; + + // Poll Events (registered events) + // NOTE: Activity is paused if not enabled (CORE.Android.appEnabled) + while ((pollResult = ALooper_pollAll(CORE.Android.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) + { + // Process this event + if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); + + // NOTE: Never close window, native activity is controlled by the system! + if (CORE.Android.app->destroyRequested != 0) + { + //CORE.Window.shouldClose = true; + //ANativeActivity_finish(CORE.Android.app->activity); + } + } +#endif + +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI) + // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin, + // we now use both methods inside here. 2nd method is still used for legacy purposes (Allows for input trough SSH console) + ProcessKeyboard(); + + // NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread() + // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() +#endif +} + +// Copy back buffer to front buffers +static void SwapBuffers(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + glfwSwapBuffers(CORE.Window.handle); +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + eglSwapBuffers(CORE.Window.device, CORE.Window.surface); + +#if defined(PLATFORM_DRM) + if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); + abort(); + } + + struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface); + if (!bo) + { + TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); + abort(); + } + + uint32_t fb = 0; + int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, + CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); + abort(); + } + + result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, + &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); + abort(); + } + + if (CORE.Window.prevFB) + { + result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); + abort(); + } + } + CORE.Window.prevFB = fb; + + if (CORE.Window.prevBO) + { + gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); + } + CORE.Window.prevBO = bo; +#endif // PLATFORM_DRM +#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP +} + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) +// GLFW3 Error Callback, runs on GLFW3 error +static void ErrorCallback(int error, const char *description) +{ + TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); +} + + +// GLFW3 WindowSize Callback, runs when window is resizedLastFrame +// NOTE: Window resizing not allowed by default +static void WindowSizeCallback(GLFWwindow *window, int width, int height) +{ + SetupViewport(width, height); // Reset viewport and projection matrix for new size + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return; + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + // NOTE: Postprocessing texture is not scaled to new size + +} + +// GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowIconifyCallback(GLFWwindow *window, int iconified) +{ + if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified + else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored +} + +#if !defined(PLATFORM_WEB) +// GLFW3 WindowMaximize Callback, runs when window is maximized/restored +static void WindowMaximizeCallback(GLFWwindow *window, int maximized) +{ + if (maximized) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized + else CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored +} +#endif + +// GLFW3 WindowFocus Callback, runs when window get/lose focus +static void WindowFocusCallback(GLFWwindow *window, int focused) +{ + if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused + else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus +} + +// GLFW3 Keyboard Callback, runs on key pressed +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + //TRACELOG(LOG_DEBUG, "Key Callback: KEY:%i(%c) - SCANCODE:%i (STATE:%i)", key, key, scancode, action); + + if (key == CORE.Input.Keyboard.exitKey && action == GLFW_PRESS) + { + glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE); + + // NOTE: Before closing window, while loop must be left! + } +#if defined(SUPPORT_SCREEN_CAPTURE) + else if (key == GLFW_KEY_F12 && action == GLFW_PRESS) + { +#if defined(SUPPORT_GIF_RECORDING) + if (mods == GLFW_MOD_CONTROL) + { + if (gifRecording) + { + gifRecording = false; + + MsfGifResult result = msf_gif_end(&gifState); + + char path[512] = { 0 }; + #if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); + #else + strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); + #endif + + SaveFileData(path, result.data, (unsigned int)result.dataSize); + msf_gif_free(result); + + #if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1))); + #endif + + TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); + } + else + { + gifRecording = true; + gifFramesCounter = 0; + + msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height); + screenshotCounter++; + + TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); + } + } + else +#endif // SUPPORT_GIF_RECORDING + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + } +#endif // SUPPORT_SCREEN_CAPTURE + else + { + // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 + // to work properly with our implementation (IsKeyDown/IsKeyUp checks) + if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; + else CORE.Input.Keyboard.currentKeyState[key] = 1; + + // Check if there is space available in the key queue + if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } +} + +// GLFW3 Char Key Callback, runs on key down (gets equivalent unicode char value) +static void CharCallback(GLFWwindow *window, unsigned int key) +{ + //TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key); + + // NOTE: Registers any key down considering OS keyboard layout but + // do not detects action events, those should be managed by user... + // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 + // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char + + // Check if there is space available in the queue + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_KEY_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key; + CORE.Input.Keyboard.charPressedQueueCount++; + } +} + +// GLFW3 Mouse Button Callback, runs on mouse button pressed +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) +{ + // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, + // but future releases may add more actions (i.e. GLFW_REPEAT) + CORE.Input.Mouse.currentButtonState[button] = action; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_UP; + + // NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback() + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +// GLFW3 Cursor Position Callback, runs on mouse move +static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) +{ + CORE.Input.Mouse.position.x = (float)x; + CORE.Input.Mouse.position.y = (float)y; + CORE.Input.Touch.position[0] = CORE.Input.Mouse.position; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_MOVE; + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +// GLFW3 Srolling Callback, runs on mouse wheel +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) +{ + CORE.Input.Mouse.currentWheelMove = (float)yoffset; +} + +// GLFW3 CursorEnter Callback, when cursor enters the window +static void CursorEnterCallback(GLFWwindow *window, int enter) +{ + if (enter == true) CORE.Input.Mouse.cursorOnScreen = true; + else CORE.Input.Mouse.cursorOnScreen = false; +} + +// GLFW3 Window Drop Callback, runs when drop files into window +// NOTE: Paths are stored in dynamic memory for further retrieval +// Everytime new files are dropped, old ones are discarded +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) +{ + ClearDroppedFiles(); + + CORE.Window.dropFilesPath = (char **)RL_MALLOC(sizeof(char *)*count); + + for (int i = 0; i < count; i++) + { + CORE.Window.dropFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); + strcpy(CORE.Window.dropFilesPath[i], paths[i]); + } + + CORE.Window.dropFilesCount = count; +} +#endif + +#if defined(PLATFORM_ANDROID) +// ANDROID: Process activity lifecycle commands +static void AndroidCommandCallback(struct android_app *app, int32_t cmd) +{ + switch (cmd) + { + case APP_CMD_START: + { + //rendering = true; + } break; + case APP_CMD_RESUME: break; + case APP_CMD_INIT_WINDOW: + { + if (app->window != NULL) + { + if (CORE.Android.contextRebindRequired) + { + // Reset screen scaling to full display size + EGLint displayFormat; + eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); + ANativeWindow_setBuffersGeometry(app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); + + // Recreate display surface and re-attach OpenGL context + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, app->window, NULL); + eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context); + + CORE.Android.contextRebindRequired = false; + } + else + { + CORE.Window.display.width = ANativeWindow_getWidth(CORE.Android.app->window); + CORE.Window.display.height = ANativeWindow_getHeight(CORE.Android.app->window); + + // Init graphics device (display device and OpenGL context) + InitGraphicsDevice(CORE.Window.screen.width, CORE.Window.screen.height); + + // Init hi-res timer + InitTimer(); + + #if defined(SUPPORT_DEFAULT_FONT) + // Load default font + // NOTE: External function (defined in module: text) + LoadFontDefault(); + Rectangle rec = GetFontDefault().recs[95]; + // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + #endif + + // TODO: GPU assets reload in case of lost focus (lost context) + // NOTE: This problem has been solved just unbinding and rebinding context from display + /* + if (assetsReloadRequired) + { + for (int i = 0; i < assetsCount; i++) + { + // TODO: Unload old asset if required + + // Load texture again to pointed texture + (*textureAsset + i) = LoadTexture(assetPath[i]); + } + } + */ + } + } + } break; + case APP_CMD_GAINED_FOCUS: + { + CORE.Android.appEnabled = true; + //ResumeMusicStream(); + } break; + case APP_CMD_PAUSE: break; + case APP_CMD_LOST_FOCUS: + { + CORE.Android.appEnabled = false; + //PauseMusicStream(); + } break; + case APP_CMD_TERM_WINDOW: + { + // Dettach OpenGL context and destroy display surface + // NOTE 1: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) + // NOTE 2: In some cases (too many context loaded), OS could unload context automatically... :( + eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(CORE.Window.device, CORE.Window.surface); + + CORE.Android.contextRebindRequired = true; + } break; + case APP_CMD_SAVE_STATE: break; + case APP_CMD_STOP: break; + case APP_CMD_DESTROY: + { + // TODO: Finish activity? + //ANativeActivity_finish(CORE.Android.app->activity); + } break; + case APP_CMD_CONFIG_CHANGED: + { + //AConfiguration_fromAssetManager(CORE.Android.app->config, CORE.Android.app->activity->assetManager); + //print_cur_config(CORE.Android.app); + + // Check screen orientation here! + } break; + default: break; + } +} + +// ANDROID: Get input events +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) +{ + // If additional inputs are required check: + // https://developer.android.com/ndk/reference/group/input + // https://developer.android.com/training/game-controllers/controller-input + + int type = AInputEvent_getType(event); + int source = AInputEvent_getSource(event); + + if (type == AINPUT_EVENT_TYPE_MOTION) + { + if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || + ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) + { + // Get first touch position + CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0); + CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0); + + // Get second touch position + CORE.Input.Touch.position[1].x = AMotionEvent_getX(event, 1); + CORE.Input.Touch.position[1].y = AMotionEvent_getY(event, 1); + + int32_t keycode = AKeyEvent_getKeyCode(event); + if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) + { + CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up + + // Stop processing gamepad buttons + return 1; + } + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + int32_t keycode = AKeyEvent_getKeyCode(event); + //int32_t AKeyEvent_getMetaState(event); + + // Save current button and its state + // NOTE: Android key action is 0 for down and 1 for up + if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) + { + CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up + + if (keycode == AKEYCODE_POWER) + { + // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS + // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS + // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. + // NOTE: AndroidManifest.xml must have + // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour + return 0; + } + else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) + { + // Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS! + return 1; + } + else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) + { + // Set default OS behaviour + return 0; + } + + return 0; + } + + CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0); + CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0); + + int32_t action = AMotionEvent_getAction(event); + unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + + if (flags == AMOTION_EVENT_ACTION_DOWN || flags == AMOTION_EVENT_ACTION_MOVE) + { + CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 1; + } + else if (flags == AMOTION_EVENT_ACTION_UP) + { + CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 0; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) + + GestureEvent gestureEvent; + + // Register touch actions + if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_DOWN; + else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_UP; + else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_MOVE; + + // Register touch points count + // NOTE: Documentation says pointerCount is Always >= 1, + // but in practice it can be 0 or over a million + gestureEvent.pointCount = AMotionEvent_getPointerCount(event); + + // Only enable gestures for 1-3 touch points + if ((gestureEvent.pointCount > 0) && (gestureEvent.pointCount < 4)) + { + // Register touch points id + // NOTE: Only two points registered + gestureEvent.pointerId[0] = AMotionEvent_getPointerId(event, 0); + gestureEvent.pointerId[1] = AMotionEvent_getPointerId(event, 1); + + // Register touch points position + gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) }; + gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) }; + + // Normalize gestureEvent.position[x] for screenWidth and screenHeight + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + gestureEvent.position[1].x /= (float)GetScreenWidth(); + gestureEvent.position[1].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); + } +#endif + + return 0; +} +#endif + +#if defined(PLATFORM_WEB) +// Register touch input events +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + for (int i = 0; i < touchEvent->numTouches; i++) + { + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE; + + // Register touch points count + gestureEvent.pointCount = touchEvent->numTouches; + + // Register touch points id + gestureEvent.pointerId[0] = touchEvent->touches[0].identifier; + gestureEvent.pointerId[1] = touchEvent->touches[1].identifier; + + // Register touch points position + // NOTE: Only two points registered + gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; + gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY }; + + double canvasWidth, canvasHeight; + // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but + // we are looking for actual CSS size: canvas.style.width and canvas.style.height + //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); + emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); + + // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth); + gestureEvent.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight); + gestureEvent.position[1].x *= ((float)GetScreenWidth()/(float)canvasWidth); + gestureEvent.position[1].y *= ((float)GetScreenHeight()/(float)canvasHeight); + + CORE.Input.Touch.position[0] = gestureEvent.position[0]; + CORE.Input.Touch.position[1] = gestureEvent.position[1]; + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#else + // Support only simple touch position + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + { + // Get first touch position + CORE.Input.Touch.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; + + double canvasWidth, canvasHeight; + //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); + emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); + + // Normalize gestureEvent.position[x] for screenWidth and screenHeight + CORE.Input.Touch.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth); + CORE.Input.Touch.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight); + } +#endif + + return 1; +} + +// Register connected/disconnected gamepads events +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + /* + TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", + gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); + + for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + */ + + if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) CORE.Input.Gamepad.ready[gamepadEvent->index] = true; + else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; + + // TODO: Test gamepadEvent->index + + return 0; +} +#endif + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + +#if defined(SUPPORT_SSH_KEYBOARD_RPI) +// Initialize Keyboard system (using standard input) +static void InitKeyboard(void) +{ + // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor + + // Make stdin non-blocking (not enough, need to configure to non-canonical mode) + int flags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags + fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified + + // Save terminal keyboard settings and reconfigure terminal with new settings + struct termios keyboardNewSettings; + tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings + keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; + + // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing + // NOTE: ISIG controls if ^C and ^Z generate break signals or not + keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); + //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); + keyboardNewSettings.c_cc[VMIN] = 1; + keyboardNewSettings.c_cc[VTIME] = 0; + + // Set new keyboard settings (change occurs immediately) + tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); + + // NOTE: Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE + + // Save old keyboard mode to restore it at the end + if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0) + { + // NOTE: It could mean we are using a remote keyboard through ssh! + TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (SSH keyboard?)"); + } + else + { + // We reconfigure keyboard mode to get: + // - scancodes (K_RAW) + // - keycodes (K_MEDIUMRAW) + // - ASCII chars (K_XLATE) + // - UNICODE chars (K_UNICODE) + ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); + } + + // Register keyboard restore when program finishes + atexit(RestoreKeyboard); +} + +// Process keyboard inputs +// TODO: Most probably input reading and processing should be in a separate thread +static void ProcessKeyboard(void) +{ + #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read + + // Keyboard input polling (fill keys[256] array with status) + int bufferByteCount = 0; // Bytes available on the buffer + char keysBuffer[MAX_KEYBUFFER_SIZE]; // Max keys to be read at a time + + // Read availables keycodes from stdin + bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call + + // Reset pressed keys array (it will be filled below) + if (bufferByteCount > 0) for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; + + // Check keys from event input workers (This is the new keyboard reading method) + //for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = CORE.Input.Keyboard.currentKeyStateEvdev[i]; + + // Fill all read bytes (looking for keys) + for (int i = 0; i < bufferByteCount; i++) + { + // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! + // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 + if (keysBuffer[i] == 0x1b) + { + // Detect ESC to stop program + if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; + else + { + if (keysBuffer[i + 1] == 0x5b) // Special function key + { + if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) + { + // Process special function keys (F1 - F12) + switch (keysBuffer[i + 3]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 + case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 + case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 + case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 + case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 + case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 + case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 + case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 + case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 + case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 + case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 + case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 + default: break; + } + + if (keysBuffer[i + 2] == 0x5b) i += 4; + else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; + } + else + { + switch (keysBuffer[i + 2]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP + case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN + case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT + case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT + default: break; + } + + i += 3; // Jump to next key + } + + // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) + } + } + } + else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with KEY_*) + { + CORE.Input.Keyboard.currentKeyState[257] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE + { + CORE.Input.Keyboard.currentKeyState[259] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else + { + // Translate lowercase a-z letters to A-Z + if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) + { + CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; + } + else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } + + // Check exit key (same functionality as GLFW3 KeyCallback()) + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + +#if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (CORE.Input.Keyboard.currentKeyState[301] == 1) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } +#endif +} + +// Restore default keyboard input +static void RestoreKeyboard(void) +{ + // Reset to default keyboard settings + tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); + + // Reconfigure keyboard to default mode + ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); +} +#endif // SUPPORT_SSH_KEYBOARD_RPI + +// Initialise user input from evdev(/dev/input/event) this means mouse, keyboard or gamepad devices +static void InitEvdevInput(void) +{ + char path[MAX_FILEPATH_LENGTH]; + DIR *directory; + struct dirent *entity; + + // Initialise keyboard file descriptor + CORE.Input.Keyboard.fd = -1; + + // Reset variables + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + CORE.Input.Touch.position[i].x = -1; + CORE.Input.Touch.position[i].y = -1; + } + + // Reset keyboard key state + for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; + + // Open the linux directory of "/dev/input" + directory = opendir(DEFAULT_EVDEV_PATH); + + if (directory) + { + while ((entity = readdir(directory)) != NULL) + { + if (strncmp("event", entity->d_name, strlen("event")) == 0) // Search for devices named "event*" + { + sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); + ConfigureEvdevDevice(path); // Configure the device if appropriate + } + } + + closedir(directory); + } + else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); +} + +// Identifies a input device and configures it for use if appropriate +static void ConfigureEvdevDevice(char *device) +{ + #define BITS_PER_LONG (8*sizeof(long)) + #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) + #define OFF(x) ((x)%BITS_PER_LONG) + #define BIT(x) (1UL<> OFF(bit)) & 1) + + struct input_absinfo absinfo; + unsigned long evBits[NBITS(EV_MAX)]; + unsigned long absBits[NBITS(ABS_MAX)]; + unsigned long relBits[NBITS(REL_MAX)]; + unsigned long keyBits[NBITS(KEY_MAX)]; + bool hasAbs = false; + bool hasRel = false; + bool hasAbsMulti = false; + int freeWorkerId = -1; + int fd = -1; + + InputEventWorker *worker; + + // Open the device and allocate worker + //------------------------------------------------------------------------------------------------------- + // Find a free spot in the workers array + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].threadId == 0) + { + freeWorkerId = i; + break; + } + } + + // Select the free worker from array + if (freeWorkerId >= 0) + { + worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker + memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker + } + else + { + TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device); + return; + } + + // Open the device + fd = open(device, O_RDONLY | O_NONBLOCK); + if (fd < 0) + { + TRACELOG(LOG_WARNING, "RPI: Failed to open input device %s", device); + return; + } + worker->fd = fd; + + // Grab number on the end of the devices name "event" + int devNum = 0; + char *ptrDevName = strrchr(device, 't'); + worker->eventNum = -1; + + if (ptrDevName != NULL) + { + if (sscanf(ptrDevName, "t%d", &devNum) == 1) + worker->eventNum = devNum; + } + + // At this point we have a connection to the device, but we don't yet know what the device is. + // It could be many things, even as simple as a power button... + //------------------------------------------------------------------------------------------------------- + + // Identify the device + //------------------------------------------------------------------------------------------------------- + ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties + + // Check for absolute input devices + if (TEST_BIT(evBits, EV_ABS)) + { + ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); + + // Check for absolute movement support (usualy touchscreens, but also joysticks) + if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) + { + hasAbs = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + + // Check for multiple absolute movement support (usualy multitouch touchscreens) + if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) + { + hasAbsMulti = true; + + // Get the scaling values + ioctl(fd, EVIOCGABS(ABS_X), &absinfo); + worker->absRange.x = absinfo.minimum; + worker->absRange.width = absinfo.maximum - absinfo.minimum; + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); + worker->absRange.y = absinfo.minimum; + worker->absRange.height = absinfo.maximum - absinfo.minimum; + } + } + + // Check for relative movement support (usualy mouse) + if (TEST_BIT(evBits, EV_REL)) + { + ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); + + if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; + } + + // Check for button support to determine the device type(usualy on all input devices) + if (TEST_BIT(evBits, EV_KEY)) + { + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); + + if (hasAbs || hasAbsMulti) + { + if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen + if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet + if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet + if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device + } + + if (hasRel) + { + if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse + if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse + } + + if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad + + if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard + } + //------------------------------------------------------------------------------------------------------- + + // Decide what to do with the device + //------------------------------------------------------------------------------------------------------- + if (worker->isKeyboard && CORE.Input.Keyboard.fd == -1) + { + // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a + // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate + // threads so that they don't drop events when the frame rate is slow. + TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device); + CORE.Input.Keyboard.fd = worker->fd; + } + else if (worker->isTouch || worker->isMouse) + { + // Looks like an interesting device + TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device, + worker->isMouse? "mouse " : "", + worker->isMultitouch? "multitouch " : "", + worker->isTouch? "touchscreen " : "", + worker->isGamepad? "gamepad " : ""); + + // Create a thread for this device + int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); + if (error != 0) + { + TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error); + worker->threadId = 0; + close(fd); + } + +#if defined(USE_LAST_TOUCH_DEVICE) + // Find touchscreen with the highest index + int maxTouchNumber = -1; + + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; + } + + // Find touchscreens with lower indexes + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) + { + if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) + { + if (CORE.Input.eventWorker[i].threadId != 0) + { + TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i); + pthread_cancel(CORE.Input.eventWorker[i].threadId); + close(CORE.Input.eventWorker[i].fd); + } + } + } +#endif + } + else close(fd); // We are not interested in this device + //------------------------------------------------------------------------------------------------------- +} + +static void PollKeyboardEvents(void) +{ + // Scancode to keycode mapping for US keyboards + // TODO: Probably replace this with a keymap from the X11 to get the correct regional map for the keyboard: + // Currently non US keyboards will have the wrong mapping for some keys + static const int keymap_US[] = + { 0,256,49,50,51,52,53,54,55,56,57,48,45,61,259,258,81,87,69,82,84, + 89,85,73,79,80,91,93,257,341,65,83,68,70,71,72,74,75,76,59,39,96, + 340,92,90,88,67,86,66,78,77,44,46,47,344,332,342,32,280,290,291, + 292,293,294,295,296,297,298,299,282,281,327,328,329,333,324,325, + 326,334,321,322,323,320,330,0,85,86,300,301,89,90,91,92,93,94,95, + 335,345,331,283,346,101,268,265,266,263,262,269,264,267,260,261, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,347,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,0,0,0,0,0,200,201,202,203,204,205,206,207,208,209,210, + 211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226, + 227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242, + 243,244,245,246,247,248,0,0,0,0,0,0,0, }; + + int fd = CORE.Input.Keyboard.fd; + if (fd == -1) return; + + struct input_event event; + int keycode; + + // Try to read data from the keyboard and only continue if successful + while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Button parsing + if (event.type == EV_KEY) + { + // Keyboard button parsing + if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 + { + keycode = keymap_US[event.code & 0xFF]; // The code we get is a scancode so we look up the apropriate keycode + + // Make sure we got a valid keycode + if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) + { + // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt + // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, + // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat + CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; + if (event.value >= 1) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + #if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (CORE.Input.Keyboard.currentKeyState[301] == 1) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + #endif + + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + + TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); + } + } + } + } +} + +// Input device events reading thread +static void *EventThread(void *arg) +{ + struct input_event event; + InputEventWorker *worker = (InputEventWorker *)arg; + + int touchAction = -1; + bool gestureUpdate = false; + + while (!CORE.Window.shouldClose) + { + // Try to read data from the device and only continue if successful + while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Relative movement parsing + if (event.type == EV_REL) + { + if (event.code == REL_X) + { + CORE.Input.Mouse.position.x += event.value; + CORE.Input.Touch.position[0].x = CORE.Input.Mouse.position.x; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + if (event.code == REL_Y) + { + CORE.Input.Mouse.position.y += event.value; + CORE.Input.Touch.position[0].y = CORE.Input.Mouse.position.y; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + if (event.code == REL_WHEEL) CORE.Input.Mouse.currentWheelMove += event.value; + } + + // Absolute movement parsing + if (event.type == EV_ABS) + { + // Basic movement + if (event.code == ABS_X) + { + CORE.Input.Mouse.position.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + if (event.code == ABS_Y) + { + CORE.Input.Mouse.position.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_MOVE; + gestureUpdate = true; + #endif + } + + // Multitouch movement + if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events + + if (event.code == ABS_MT_POSITION_X) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + } + + if (event.code == ABS_MT_POSITION_Y) + { + if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + } + + if (event.code == ABS_MT_TRACKING_ID) + { + if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) + { + // Touch has ended for this point + CORE.Input.Touch.position[worker->touchSlot].x = -1; + CORE.Input.Touch.position[worker->touchSlot].y = -1; + } + } + + // Touchscreen tap + if (event.code == ABS_PRESSURE) + { + int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON]; + + if (!event.value && previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = 0; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_UP; + gestureUpdate = true; + #endif + } + + if (event.value && !previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = 1; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_DOWN; + gestureUpdate = true; + #endif + } + } + + } + + // Button parsing + if (event.type == EV_KEY) + { + // Mouse button parsing + if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = event.value; + + #if defined(SUPPORT_GESTURES_SYSTEM) + if (event.value > 0) touchAction = TOUCH_DOWN; + else touchAction = TOUCH_UP; + gestureUpdate = true; + #endif + } + + if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_RIGHT_BUTTON] = event.value; + if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_MIDDLE_BUTTON] = event.value; + } + + // Screen confinement + if (!CORE.Input.Mouse.cursorHidden) + { + if (CORE.Input.Mouse.position.x < 0) CORE.Input.Mouse.position.x = 0; + if (CORE.Input.Mouse.position.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.position.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; + + if (CORE.Input.Mouse.position.y < 0) CORE.Input.Mouse.position.y = 0; + if (CORE.Input.Mouse.position.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.position.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; + } + + // Gesture update + if (gestureUpdate) + { + #if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + gestureEvent.pointCount = 0; + gestureEvent.touchAction = touchAction; + + if (CORE.Input.Touch.position[0].x >= 0) gestureEvent.pointCount++; + if (CORE.Input.Touch.position[1].x >= 0) gestureEvent.pointCount++; + if (CORE.Input.Touch.position[2].x >= 0) gestureEvent.pointCount++; + if (CORE.Input.Touch.position[3].x >= 0) gestureEvent.pointCount++; + + gestureEvent.pointerId[0] = 0; + gestureEvent.pointerId[1] = 1; + gestureEvent.pointerId[2] = 2; + gestureEvent.pointerId[3] = 3; + + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + gestureEvent.position[1] = CORE.Input.Touch.position[1]; + gestureEvent.position[2] = CORE.Input.Touch.position[2]; + gestureEvent.position[3] = CORE.Input.Touch.position[3]; + + ProcessGestureEvent(gestureEvent); + #endif + } + } + Wait(5); // Sleep for 5ms to avoid hogging CPU time + } + + close(worker->fd); + + return NULL; +} + +// Init gamepad system +static void InitGamepad(void) +{ + char gamepadDev[128] = ""; + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); + + if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY|O_NONBLOCK)) < 0) + { + // NOTE: Only show message for first gamepad + if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available"); + } + else + { + CORE.Input.Gamepad.ready[i] = true; + + // NOTE: Only create one thread + if (i == 0) + { + int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL); + + if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread"); + else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully"); + } + } + } +} + +// Process Gamepad (/dev/input/js0) +static void *GamepadThread(void *arg) +{ + #define JS_EVENT_BUTTON 0x01 // Button pressed/released + #define JS_EVENT_AXIS 0x02 // Joystick axis moved + #define JS_EVENT_INIT 0x80 // Initial state of device + + struct js_event { + unsigned int time; // event timestamp in milliseconds + short value; // event value + unsigned char type; // event type + unsigned char number; // event axis/button number + }; + + // Read gamepad event + struct js_event gamepadEvent; + + while (!CORE.Window.shouldClose) + { + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) + { + gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events + + // Process gamepad events by type + if (gamepadEvent.type == JS_EVENT_BUTTON) + { + //TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + + if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) + { + // 1 - button pressed, 0 - button released + CORE.Input.Gamepad.currentState[i][gamepadEvent.number] = (int)gamepadEvent.value; + + if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number; + else CORE.Input.Gamepad.lastButtonPressed = -1; + } + } + else if (gamepadEvent.type == JS_EVENT_AXIS) + { + //TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + + if (gamepadEvent.number < MAX_GAMEPAD_AXIS) + { + // NOTE: Scaling of gamepadEvent.value to get values between -1..1 + CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; + } + } + } + else Wait(1); // Sleep for 1 ms to avoid hogging CPU time + } + } + + return NULL; +} +#endif // PLATFORM_RPI || PLATFORM_DRM + +#if defined(PLATFORM_UWP) +// UWP function pointers +// NOTE: Those pointers are set by UWP App +static UWPQueryTimeFunc uwpQueryTimeFunc = NULL; +static UWPSleepFunc uwpSleepFunc = NULL; +static UWPDisplaySizeFunc uwpDisplaySizeFunc = NULL; +static UWPMouseFunc uwpMouseLockFunc = NULL; +static UWPMouseFunc uwpMouseUnlockFunc = NULL; +static UWPMouseFunc uwpMouseShowFunc = NULL; +static UWPMouseFunc uwpMouseHideFunc = NULL; +static UWPMouseSetPosFunc uwpMouseSetPosFunc = NULL; +static void *uwpCoreWindow = NULL; + +// Check all required UWP function pointers have been set +bool UWPIsConfigured() +{ + bool pass = true; + + if (uwpQueryTimeFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetQueryTimeFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpSleepFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetSleepFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpDisplaySizeFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetDisplaySizeFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseLockFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseLockFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseUnlockFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseUnlockFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseShowFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseShowFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseHideFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseHideFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpMouseSetPosFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseSetPosFunc() must be called with a valid function before InitWindow()"); pass = false; } + if (uwpCoreWindow == NULL) { TRACELOG(LOG_ERROR, "UWP: A pointer to the UWP core window must be set before InitWindow()"); pass = false; } + + return pass; +} + +// UWP function handlers get/set +void UWPSetDataPath(const char* path) { CORE.UWP.internalDataPath = path; } +UWPQueryTimeFunc UWPGetQueryTimeFunc(void) { return uwpQueryTimeFunc; } +void UWPSetQueryTimeFunc(UWPQueryTimeFunc func) { uwpQueryTimeFunc = func; } +UWPSleepFunc UWPGetSleepFunc(void) { return uwpSleepFunc; } +void UWPSetSleepFunc(UWPSleepFunc func) { uwpSleepFunc = func; } +UWPDisplaySizeFunc UWPGetDisplaySizeFunc(void) { return uwpDisplaySizeFunc; } +void UWPSetDisplaySizeFunc(UWPDisplaySizeFunc func) { uwpDisplaySizeFunc = func; } +UWPMouseFunc UWPGetMouseLockFunc() { return uwpMouseLockFunc; } +void UWPSetMouseLockFunc(UWPMouseFunc func) { uwpMouseLockFunc = func; } +UWPMouseFunc UWPGetMouseUnlockFunc() { return uwpMouseUnlockFunc; } +void UWPSetMouseUnlockFunc(UWPMouseFunc func) { uwpMouseUnlockFunc = func; } +UWPMouseFunc UWPGetMouseShowFunc() { return uwpMouseShowFunc; } +void UWPSetMouseShowFunc(UWPMouseFunc func) { uwpMouseShowFunc = func; } +UWPMouseFunc UWPGetMouseHideFunc() { return uwpMouseHideFunc; } +void UWPSetMouseHideFunc(UWPMouseFunc func) { uwpMouseHideFunc = func; } +UWPMouseSetPosFunc UWPGetMouseSetPosFunc() { return uwpMouseSetPosFunc; } +void UWPSetMouseSetPosFunc(UWPMouseSetPosFunc func) { uwpMouseSetPosFunc = func; } + +void *UWPGetCoreWindowPtr() { return uwpCoreWindow; } +void UWPSetCoreWindowPtr(void* ptr) { uwpCoreWindow = ptr; } +void UWPMouseWheelEvent(int deltaY) { CORE.Input.Mouse.currentWheelMove = (float)deltaY; } + +void UWPKeyDownEvent(int key, bool down, bool controlKey) +{ + if (key == CORE.Input.Keyboard.exitKey && down) + { + // Time to close the window. + CORE.Window.shouldClose = true; + } +#if defined(SUPPORT_SCREEN_CAPTURE) + else if (key == KEY_F12 && down) + { +#if defined(SUPPORT_GIF_RECORDING) + if (controlKey) + { + if (gifRecording) + { + gifRecording = false; + + MsfGifResult result = msf_gif_end(&gifState); + + SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.UWP.internalDataPath, screenshotCounter), result.data, result.dataSize); + msf_gif_free(result); + +#if defined(PLATFORM_WEB) + // Download file from MEMFS (emscripten memory filesystem) + // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html + emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1))); +#endif + TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); + } + else + { + gifRecording = true; + gifFramesCounter = 0; + + msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height); + screenshotCounter++; + + TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); + } + } + else +#endif // SUPPORT_GIF_RECORDING + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + } +#endif // SUPPORT_SCREEN_CAPTURE + else + { + CORE.Input.Keyboard.currentKeyState[key] = down; + } +} + +void UWPKeyCharEvent(int key) +{ + if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } +} + +void UWPMouseButtonEvent(int button, bool down) +{ + CORE.Input.Mouse.currentButtonState[button] = down; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_UP; + + // NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback() + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +void UWPMousePosEvent(double x, double y) +{ + CORE.Input.Mouse.position.x = (float)x; + CORE.Input.Mouse.position.y = (float)y; + CORE.Input.Touch.position[0] = CORE.Input.Mouse.position; + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_MOVE; + + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Mouse.position; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +void UWPResizeEvent(int width, int height) +{ + SetupViewport(width, height); // Reset viewport and projection matrix for new size + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + + // NOTE: Postprocessing texture is not scaled to new size + + CORE.Window.resizedLastFrame = true; +} + +void UWPActivateGamepadEvent(int gamepad, bool active) +{ + if (gamepad < MAX_GAMEPADS) CORE.Input.Gamepad.ready[gamepad] = active; +} + +void UWPRegisterGamepadButton(int gamepad, int button, bool down) +{ + if (gamepad < MAX_GAMEPADS) + { + if (button < MAX_GAMEPAD_BUTTONS) + { + CORE.Input.Gamepad.currentState[gamepad][button] = down; + CORE.Input.Gamepad.lastButtonPressed = button; + } + } +} + +void UWPRegisterGamepadAxis(int gamepad, int axis, float value) +{ + if (gamepad < MAX_GAMEPADS) + { + if (axis < MAX_GAMEPAD_AXIS) CORE.Input.Gamepad.axisState[gamepad][axis] = value; + } +} + +void UWPGestureMove(int pointer, float x, float y) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + // Assign the pointer ID and touch action + gestureEvent.pointerId[0] = pointer; + gestureEvent.touchAction = TOUCH_MOVE; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0].x = x; + gestureEvent.position[0].y = y; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +void UWPGestureTouch(int pointer, float x, float y, bool touch) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + // Assign the pointer ID and touch action + gestureEvent.pointerId[0] = pointer; + gestureEvent.touchAction = touch ? TOUCH_DOWN : TOUCH_UP; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0].x = x; + gestureEvent.position[0].y = y; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +#endif // PLATFORM_UWP + +#if defined(PLATFORM_DRM) +// Search matching DRM mode in connector's mode list +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) +{ + if (NULL == connector) return -1; + if (NULL == mode) return -1; + + // safe bitwise comparison of two modes + #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b)) + + for (size_t i = 0; i < connector->count_modes; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, + connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i; + } + + return -1; + + #undef BINCMP +} + +// Search exactly matching DRM connector mode in connector's list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); + + if (NULL == connector) return -1; + + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue; + + if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; + } + + TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); + return -1; +} + +// Search the nearest matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); + + if (NULL == connector) return -1; + + int nearestIndex = -1; + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->hdisplay < width) || (mode->vdisplay < height) | (mode->vrefresh < fps)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); + continue; + } + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); + continue; + } + + if ((mode->hdisplay >= width) && (mode->vdisplay >= height) && (mode->vrefresh >= fps)) + { + const int widthDiff = mode->hdisplay - width; + const int heightDiff = mode->vdisplay - height; + const int fpsDiff = mode->vrefresh - fps; + + if (nearestIndex < 0) + { + nearestIndex = i; + continue; + } + + const int nearestWidthDiff = CORE.Window.connector->modes[nearestIndex].hdisplay - width; + const int nearestHeightDiff = CORE.Window.connector->modes[nearestIndex].vdisplay - height; + const int nearestFpsDiff = CORE.Window.connector->modes[nearestIndex].vrefresh - fps; + + if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) nearestIndex = i; + } + } + + return nearestIndex; +} +#endif diff --git a/raylib_pi4_test/easings.h b/raylib_pi4_test/easings.h new file mode 100644 index 0000000..3441305 --- /dev/null +++ b/raylib_pi4_test/easings.h @@ -0,0 +1,263 @@ +/******************************************************************************************* +* +* raylib easings (header only file) +* +* Useful easing functions for values animation +* +* This header uses: +* #define EASINGS_STATIC_INLINE // Inlines all functions code, so it runs faster. +* // This requires lots of memory on system. +* How to use: +* The four inputs t,b,c,d are defined as follows: +* t = current time (in any unit measure, but same unit as duration) +* b = starting value to interpolate +* c = the total change in value of b that needs to occur +* d = total time it should take to complete (duration) +* +* Example: +* +* int currentTime = 0; +* int duration = 100; +* float startPositionX = 0.0f; +* float finalPositionX = 30.0f; +* float currentPositionX = startPositionX; +* +* while (currentPositionX < finalPositionX) +* { +* currentPositionX = EaseSineIn(currentTime, startPositionX, finalPositionX - startPositionX, duration); +* currentTime++; +* } +* +* A port of Robert Penner's easing equations to C (http://robertpenner.com/easing/) +* +* Robert Penner License +* --------------------------------------------------------------------------------- +* Open source under the BSD License. +* +* Copyright (c) 2001 Robert Penner. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* - Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* - Neither the name of the author nor the names of contributors may be used +* to endorse or promote products derived from this software without specific +* prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. +* --------------------------------------------------------------------------------- +* +* Copyright (c) 2015 Ramon Santamaria +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef EASINGS_H +#define EASINGS_H + +#define EASINGS_STATIC_INLINE // NOTE: By default, compile functions as static inline + +#if defined(EASINGS_STATIC_INLINE) + #define EASEDEF static inline +#else + #define EASEDEF extern +#endif + +#include // Required for: sinf(), cosf(), sqrtf(), powf() + +#ifndef PI + #define PI 3.14159265358979323846f //Required as PI is not always defined in math.h +#endif + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +// Linear Easing functions +EASEDEF float EaseLinearNone(float t, float b, float c, float d) { return (c*t/d + b); } +EASEDEF float EaseLinearIn(float t, float b, float c, float d) { return (c*t/d + b); } +EASEDEF float EaseLinearOut(float t, float b, float c, float d) { return (c*t/d + b); } +EASEDEF float EaseLinearInOut(float t,float b, float c, float d) { return (c*t/d + b); } + +// Sine Easing functions +EASEDEF float EaseSineIn(float t, float b, float c, float d) { return (-c*cosf(t/d*(PI/2.0f)) + c + b); } +EASEDEF float EaseSineOut(float t, float b, float c, float d) { return (c*sinf(t/d*(PI/2.0f)) + b); } +EASEDEF float EaseSineInOut(float t, float b, float c, float d) { return (-c/2.0f*(cosf(PI*t/d) - 1.0f) + b); } + +// Circular Easing functions +EASEDEF float EaseCircIn(float t, float b, float c, float d) { t /= d; return (-c*(sqrtf(1.0f - t*t) - 1.0f) + b); } +EASEDEF float EaseCircOut(float t, float b, float c, float d) { t = t/d - 1.0f; return (c*sqrtf(1.0f - t*t) + b); } +EASEDEF float EaseCircInOut(float t, float b, float c, float d) +{ + if ((t/=d/2.0f) < 1.0f) return (-c/2.0f*(sqrtf(1.0f - t*t) - 1.0f) + b); + t -= 2.0f; return (c/2.0f*(sqrtf(1.0f - t*t) + 1.0f) + b); +} + +// Cubic Easing functions +EASEDEF float EaseCubicIn(float t, float b, float c, float d) { t /= d; return (c*t*t*t + b); } +EASEDEF float EaseCubicOut(float t, float b, float c, float d) { t = t/d - 1.0f; return (c*(t*t*t + 1.0f) + b); } +EASEDEF float EaseCubicInOut(float t, float b, float c, float d) +{ + if ((t/=d/2.0f) < 1.0f) return (c/2.0f*t*t*t + b); + t -= 2.0f; return (c/2.0f*(t*t*t + 2.0f) + b); +} + +// Quadratic Easing functions +EASEDEF float EaseQuadIn(float t, float b, float c, float d) { t /= d; return (c*t*t + b); } +EASEDEF float EaseQuadOut(float t, float b, float c, float d) { t /= d; return (-c*t*(t - 2.0f) + b); } +EASEDEF float EaseQuadInOut(float t, float b, float c, float d) +{ + if ((t/=d/2) < 1) return (((c/2)*(t*t)) + b); + return (-c/2.0f*(((t - 1.0f)*(t - 3.0f)) - 1.0f) + b); +} + +// Exponential Easing functions +EASEDEF float EaseExpoIn(float t, float b, float c, float d) { return (t == 0.0f) ? b : (c*powf(2.0f, 10.0f*(t/d - 1.0f)) + b); } +EASEDEF float EaseExpoOut(float t, float b, float c, float d) { return (t == d) ? (b + c) : (c*(-powf(2.0f, -10.0f*t/d) + 1.0f) + b); } +EASEDEF float EaseExpoInOut(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if (t == d) return (b + c); + if ((t/=d/2.0f) < 1.0f) return (c/2.0f*powf(2.0f, 10.0f*(t - 1.0f)) + b); + + return (c/2.0f*(-powf(2.0f, -10.0f*(t - 1.0f)) + 2.0f) + b); +} + +// Back Easing functions +EASEDEF float EaseBackIn(float t, float b, float c, float d) +{ + float s = 1.70158f; + float postFix = t/=d; + return (c*(postFix)*t*((s + 1.0f)*t - s) + b); +} + +EASEDEF float EaseBackOut(float t, float b, float c, float d) +{ + float s = 1.70158f; + t = t/d - 1.0f; + return (c*(t*t*((s + 1.0f)*t + s) + 1.0f) + b); +} + +EASEDEF float EaseBackInOut(float t, float b, float c, float d) +{ + float s = 1.70158f; + if ((t/=d/2.0f) < 1.0f) + { + s *= 1.525f; + return (c/2.0f*(t*t*((s + 1.0f)*t - s)) + b); + } + + float postFix = t-=2.0f; + s *= 1.525f; + return (c/2.0f*((postFix)*t*((s + 1.0f)*t + s) + 2.0f) + b); +} + +// Bounce Easing functions +EASEDEF float EaseBounceOut(float t, float b, float c, float d) +{ + if ((t/=d) < (1.0f/2.75f)) + { + return (c*(7.5625f*t*t) + b); + } + else if (t < (2.0f/2.75f)) + { + float postFix = t-=(1.5f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.75f) + b); + } + else if (t < (2.5/2.75)) + { + float postFix = t-=(2.25f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.9375f) + b); + } + else + { + float postFix = t-=(2.625f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.984375f) + b); + } +} + +EASEDEF float EaseBounceIn(float t, float b, float c, float d) { return (c - EaseBounceOut(d - t, 0.0f, c, d) + b); } +EASEDEF float EaseBounceInOut(float t, float b, float c, float d) +{ + if (t < d/2.0f) return (EaseBounceIn(t*2.0f, 0.0f, c, d)*0.5f + b); + else return (EaseBounceOut(t*2.0f - d, 0.0f, c, d)*0.5f + c*0.5f + b); +} + +// Elastic Easing functions +EASEDEF float EaseElasticIn(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if ((t/=d) == 1.0f) return (b + c); + + float p = d*0.3f; + float a = c; + float s = p/4.0f; + float postFix = a*powf(2.0f, 10.0f*(t-=1.0f)); + + return (-(postFix*sinf((t*d-s)*(2.0f*PI)/p )) + b); +} + +EASEDEF float EaseElasticOut(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if ((t/=d) == 1.0f) return (b + c); + + float p = d*0.3f; + float a = c; + float s = p/4.0f; + + return (a*powf(2.0f,-10.0f*t)*sinf((t*d-s)*(2.0f*PI)/p) + c + b); +} + +EASEDEF float EaseElasticInOut(float t, float b, float c, float d) +{ + if (t == 0.0f) return b; + if ((t/=d/2.0f) == 2.0f) return (b + c); + + float p = d*(0.3f*1.5f); + float a = c; + float s = p/4.0f; + + if (t < 1.0f) + { + float postFix = a*powf(2.0f, 10.0f*(t-=1.0f)); + return -0.5f*(postFix*sinf((t*d-s)*(2.0f*PI)/p)) + b; + } + + float postFix = a*powf(2.0f, -10.0f*(t-=1.0f)); + + return (postFix*sinf((t*d-s)*(2.0f*PI)/p)*0.5f + c + b); +} + +#ifdef __cplusplus +} +#endif + +#endif // EASINGS_H diff --git a/raylib_pi4_test/gestures.h b/raylib_pi4_test/gestures.h new file mode 100644 index 0000000..ece65b3 --- /dev/null +++ b/raylib_pi4_test/gestures.h @@ -0,0 +1,559 @@ +/********************************************************************************************** +* +* raylib.gestures - Gestures system, gestures processing based on input events (touch/mouse) +* +* NOTE: Memory footprint of this library is aproximately 128 bytes (global variables) +* +* CONFIGURATION: +* +* #define GESTURES_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define GESTURES_STANDALONE +* If defined, the library can be used as standalone to process gesture events with +* no external dependencies. +* +* CONTRIBUTORS: +* Marc Palau: Initial implementation (2014) +* Albert Martos: Complete redesign and testing (2015) +* Ian Eito: Complete redesign and testing (2015) +* Ramon Santamaria: Supervision, review, update and maintenance +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef GESTURES_H +#define GESTURES_H + +#ifndef PI + #define PI 3.14159265358979323846 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for GESTURES_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(GESTURES_STANDALONE) + #ifndef __cplusplus + // Boolean type + typedef enum { false, true } bool; + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Gestures type + // NOTE: It could be used as flags to enable only some gestures + typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP = 1, + GESTURE_DOUBLETAP = 2, + GESTURE_HOLD = 4, + GESTURE_DRAG = 8, + GESTURE_SWIPE_RIGHT = 16, + GESTURE_SWIPE_LEFT = 32, + GESTURE_SWIPE_UP = 64, + GESTURE_SWIPE_DOWN = 128, + GESTURE_PINCH_IN = 256, + GESTURE_PINCH_OUT = 512 + } Gestures; +#endif + +typedef enum { TOUCH_UP, TOUCH_DOWN, TOUCH_MOVE } TouchAction; + +// Gesture event +// NOTE: MAX_TOUCH_POINTS fixed to 4 +typedef struct { + int touchAction; + int pointCount; + int pointerId[4]; + Vector2 position[4]; +} GestureEvent; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +void ProcessGestureEvent(GestureEvent event); // Process gesture event and translate it into gestures +void UpdateGestures(void); // Update gestures detected (must be called every frame) + +#if defined(GESTURES_STANDALONE) +void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +bool IsGestureDetected(int gesture); // Check if a gesture have been detected +int GetGestureDetected(void); // Get latest detected gesture +int GetTouchPointsCount(void); // Get touch points count +float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds +Vector2 GetGestureDragVector(void); // Get gesture drag vector +float GetGestureDragAngle(void); // Get gesture drag angle +Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +float GetGesturePinchAngle(void); // Get gesture pinch angle +#endif + +#ifdef __cplusplus +} +#endif + +#endif // GESTURES_H + +/*********************************************************************************** +* +* GESTURES IMPLEMENTATION +* +************************************************************************************/ + +#if defined(GESTURES_IMPLEMENTATION) + +#if defined(_WIN32) + // Functions required to query time on Windows + int __stdcall QueryPerformanceCounter(unsigned long long int *lpPerformanceCount); + int __stdcall QueryPerformanceFrequency(unsigned long long int *lpFrequency); +#elif defined(__linux__) + #if _POSIX_C_SOURCE < 199309L + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. + #endif + #include // Required for: timespec + #include // Required for: clock_gettime() + + #include // Required for: sqrtf(), atan2f() +#endif +#if defined(__APPLE__) // macOS also defines __MACH__ + #include // Required for: clock_get_time() + #include // Required for: mach_timespec_t +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define FORCE_TO_SWIPE 0.0005f // Swipe force, measured in normalized screen units/time +#define MINIMUM_DRAG 0.015f // Drag minimum force, measured in normalized screen units (0.0f to 1.0f) +#define MINIMUM_PINCH 0.005f // Pinch minimum force, measured in normalized screen units (0.0f to 1.0f) +#define TAP_TIMEOUT 300 // Tap minimum time, measured in milliseconds +#define PINCH_TIMEOUT 300 // Pinch minimum time, measured in milliseconds +#define DOUBLETAP_RANGE 0.03f // DoubleTap range, measured in normalized screen units (0.0f to 1.0f) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Gestures module state context [136 bytes] +typedef struct { + unsigned int current; // Current detected gesture + unsigned int enabledFlags; // Enabled gestures flags + struct { + int firstId; // Touch id for first touch point + int pointCount; // Touch points counter + double eventTime; // Time stamp when an event happened + Vector2 upPosition; // Touch up position + Vector2 downPositionA; // First touch down position + Vector2 downPositionB; // Second touch down position + Vector2 downDragPosition; // Touch drag position + Vector2 moveDownPositionA; // First touch down position on move + Vector2 moveDownPositionB; // Second touch down position on move + int tapCounter; // TAP counter (one tap implies TOUCH_DOWN and TOUCH_UP actions) + } Touch; + struct { + bool resetRequired; // HOLD reset to get first touch point again + double timeDuration; // HOLD duration in milliseconds + } Hold; + struct { + Vector2 vector; // DRAG vector (between initial and current position) + float angle; // DRAG angle (relative to x-axis) + float distance; // DRAG distance (from initial touch point to final) (normalized [0..1]) + float intensity; // DRAG intensity, how far why did the DRAG (pixels per frame) + } Drag; + struct { + bool start; // SWIPE used to define when start measuring GESTURES.Swipe.timeDuration + double timeDuration; // SWIPE time to calculate drag intensity + } Swipe; + struct { + Vector2 vector; // PINCH vector (between first and second touch points) + float angle; // PINCH angle (relative to x-axis) + float distance; // PINCH displacement distance (normalized [0..1]) + } Pinch; +} GesturesData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GesturesData GESTURES = { + .Touch.firstId = -1, + .current = GESTURE_NONE, // No current gesture detected + .enabledFlags = 0b0000001111111111 // All gestures supported by default +}; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GESTURES_STANDALONE) +// Some required math functions provided by raymath.h +static float Vector2Angle(Vector2 initialPosition, Vector2 finalPosition); +static float Vector2Distance(Vector2 v1, Vector2 v2); +#endif +static double GetCurrentTime(void); + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Enable only desired getures to be detected +void SetGesturesEnabled(unsigned int flags) +{ + GESTURES.enabledFlags = flags; +} + +// Check if a gesture have been detected +bool IsGestureDetected(int gesture) +{ + if ((GESTURES.enabledFlags & GESTURES.current) == gesture) return true; + else return false; +} + +// Process gesture event and translate it into gestures +void ProcessGestureEvent(GestureEvent event) +{ + // Reset required variables + GESTURES.Touch.pointCount = event.pointCount; // Required on UpdateGestures() + + if (GESTURES.Touch.pointCount < 2) + { + if (event.touchAction == TOUCH_DOWN) + { + GESTURES.Touch.tapCounter++; // Tap counter + + // Detect GESTURE_DOUBLE_TAP + if ((GESTURES.current == GESTURE_NONE) && (GESTURES.Touch.tapCounter >= 2) && ((GetCurrentTime() - GESTURES.Touch.eventTime) < TAP_TIMEOUT) && (Vector2Distance(GESTURES.Touch.downPositionA, event.position[0]) < DOUBLETAP_RANGE)) + { + GESTURES.current = GESTURE_DOUBLETAP; + GESTURES.Touch.tapCounter = 0; + } + else // Detect GESTURE_TAP + { + GESTURES.Touch.tapCounter = 1; + GESTURES.current = GESTURE_TAP; + } + + GESTURES.Touch.downPositionA = event.position[0]; + GESTURES.Touch.downDragPosition = event.position[0]; + + GESTURES.Touch.upPosition = GESTURES.Touch.downPositionA; + GESTURES.Touch.eventTime = GetCurrentTime(); + + GESTURES.Touch.firstId = event.pointerId[0]; + + GESTURES.Drag.vector = (Vector2){ 0.0f, 0.0f }; + } + else if (event.touchAction == TOUCH_UP) + { + if (GESTURES.current == GESTURE_DRAG) GESTURES.Touch.upPosition = event.position[0]; + + // NOTE: GESTURES.Drag.intensity dependend on the resolution of the screen + GESTURES.Drag.distance = Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); + GESTURES.Drag.intensity = GESTURES.Drag.distance/(float)((GetCurrentTime() - GESTURES.Swipe.timeDuration)); + + GESTURES.Swipe.start = false; + + // Detect GESTURE_SWIPE + if ((GESTURES.Drag.intensity > FORCE_TO_SWIPE) && (GESTURES.Touch.firstId == event.pointerId[0])) + { + // NOTE: Angle should be inverted in Y + GESTURES.Drag.angle = 360.0f - Vector2Angle(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); + + if ((GESTURES.Drag.angle < 30) || (GESTURES.Drag.angle > 330)) GESTURES.current = GESTURE_SWIPE_RIGHT; // Right + else if ((GESTURES.Drag.angle > 30) && (GESTURES.Drag.angle < 120)) GESTURES.current = GESTURE_SWIPE_UP; // Up + else if ((GESTURES.Drag.angle > 120) && (GESTURES.Drag.angle < 210)) GESTURES.current = GESTURE_SWIPE_LEFT; // Left + else if ((GESTURES.Drag.angle > 210) && (GESTURES.Drag.angle < 300)) GESTURES.current = GESTURE_SWIPE_DOWN; // Down + else GESTURES.current = GESTURE_NONE; + } + else + { + GESTURES.Drag.distance = 0.0f; + GESTURES.Drag.intensity = 0.0f; + GESTURES.Drag.angle = 0.0f; + + GESTURES.current = GESTURE_NONE; + } + + GESTURES.Touch.downDragPosition = (Vector2){ 0.0f, 0.0f }; + GESTURES.Touch.pointCount = 0; + } + else if (event.touchAction == TOUCH_MOVE) + { + if (GESTURES.current == GESTURE_DRAG) GESTURES.Touch.eventTime = GetCurrentTime(); + + if (!GESTURES.Swipe.start) + { + GESTURES.Swipe.timeDuration = GetCurrentTime(); + GESTURES.Swipe.start = true; + } + + GESTURES.Touch.moveDownPositionA = event.position[0]; + + if (GESTURES.current == GESTURE_HOLD) + { + if (GESTURES.Hold.resetRequired) GESTURES.Touch.downPositionA = event.position[0]; + + GESTURES.Hold.resetRequired = false; + + // Detect GESTURE_DRAG + if (Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.moveDownPositionA) >= MINIMUM_DRAG) + { + GESTURES.Touch.eventTime = GetCurrentTime(); + GESTURES.current = GESTURE_DRAG; + } + } + + GESTURES.Drag.vector.x = GESTURES.Touch.moveDownPositionA.x - GESTURES.Touch.downDragPosition.x; + GESTURES.Drag.vector.y = GESTURES.Touch.moveDownPositionA.y - GESTURES.Touch.downDragPosition.y; + } + } + else // Two touch points + { + if (event.touchAction == TOUCH_DOWN) + { + GESTURES.Touch.downPositionA = event.position[0]; + GESTURES.Touch.downPositionB = event.position[1]; + + //GESTURES.Pinch.distance = Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.downPositionB); + + GESTURES.Pinch.vector.x = GESTURES.Touch.downPositionB.x - GESTURES.Touch.downPositionA.x; + GESTURES.Pinch.vector.y = GESTURES.Touch.downPositionB.y - GESTURES.Touch.downPositionA.y; + + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + } + else if (event.touchAction == TOUCH_MOVE) + { + GESTURES.Pinch.distance = Vector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); + + GESTURES.Touch.downPositionA = GESTURES.Touch.moveDownPositionA; + GESTURES.Touch.downPositionB = GESTURES.Touch.moveDownPositionB; + + GESTURES.Touch.moveDownPositionA = event.position[0]; + GESTURES.Touch.moveDownPositionB = event.position[1]; + + GESTURES.Pinch.vector.x = GESTURES.Touch.moveDownPositionB.x - GESTURES.Touch.moveDownPositionA.x; + GESTURES.Pinch.vector.y = GESTURES.Touch.moveDownPositionB.y - GESTURES.Touch.moveDownPositionA.y; + + if ((Vector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.moveDownPositionA) >= MINIMUM_PINCH) || (Vector2Distance(GESTURES.Touch.downPositionB, GESTURES.Touch.moveDownPositionB) >= MINIMUM_PINCH)) + { + if ((Vector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB) - GESTURES.Pinch.distance) < 0) GESTURES.current = GESTURE_PINCH_IN; + else GESTURES.current = GESTURE_PINCH_OUT; + } + else + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + } + + // NOTE: Angle should be inverted in Y + GESTURES.Pinch.angle = 360.0f - Vector2Angle(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); + } + else if (event.touchAction == TOUCH_UP) + { + GESTURES.Pinch.distance = 0.0f; + GESTURES.Pinch.angle = 0.0f; + GESTURES.Pinch.vector = (Vector2){ 0.0f, 0.0f }; + GESTURES.Touch.pointCount = 0; + + GESTURES.current = GESTURE_NONE; + } + } +} + +// Update gestures detected (must be called every frame) +void UpdateGestures(void) +{ + // NOTE: Gestures are processed through system callbacks on touch events + + // Detect GESTURE_HOLD + if (((GESTURES.current == GESTURE_TAP) || (GESTURES.current == GESTURE_DOUBLETAP)) && (GESTURES.Touch.pointCount < 2)) + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + } + + if (((GetCurrentTime() - GESTURES.Touch.eventTime) > TAP_TIMEOUT) && (GESTURES.current == GESTURE_DRAG) && (GESTURES.Touch.pointCount < 2)) + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = GetCurrentTime(); + GESTURES.Hold.resetRequired = true; + } + + // Detect GESTURE_NONE + if ((GESTURES.current == GESTURE_SWIPE_RIGHT) || (GESTURES.current == GESTURE_SWIPE_UP) || (GESTURES.current == GESTURE_SWIPE_LEFT) || (GESTURES.current == GESTURE_SWIPE_DOWN)) + { + GESTURES.current = GESTURE_NONE; + } +} + +// Get number of touch points +int GetTouchPointsCount(void) +{ + // NOTE: point count is calculated when ProcessGestureEvent(GestureEvent event) is called + + return GESTURES.Touch.pointCount; +} + +// Get latest detected gesture +int GetGestureDetected(void) +{ + // Get current gesture only if enabled + return (GESTURES.enabledFlags & GESTURES.current); +} + +// Hold time measured in ms +float GetGestureHoldDuration(void) +{ + // NOTE: time is calculated on current gesture HOLD + + double time = 0.0; + + if (GESTURES.current == GESTURE_HOLD) time = GetCurrentTime() - GESTURES.Hold.timeDuration; + + return (float)time; +} + +// Get drag vector (between initial touch point to current) +Vector2 GetGestureDragVector(void) +{ + // NOTE: drag vector is calculated on one touch points TOUCH_MOVE + + return GESTURES.Drag.vector; +} + +// Get drag angle +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetGestureDragAngle(void) +{ + // NOTE: drag angle is calculated on one touch points TOUCH_UP + + return GESTURES.Drag.angle; +} + +// Get distance between two pinch points +Vector2 GetGesturePinchVector(void) +{ + // NOTE: The position values used for GESTURES.Pinch.distance are not modified like the position values of [core.c]-->GetTouchPosition(int index) + // NOTE: pinch distance is calculated on two touch points TOUCH_MOVE + + return GESTURES.Pinch.vector; +} + +// Get angle beween two pinch points +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetGesturePinchAngle(void) +{ + // NOTE: pinch angle is calculated on two touch points TOUCH_MOVE + + return GESTURES.Pinch.angle; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GESTURES_STANDALONE) +// Returns angle from two-points vector with X-axis +static float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float angle = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); + + if (angle < 0) angle += 360.0f; + + return angle; +} + +// Calculate distance between two Vector2 +static float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + + result = (float)sqrt(dx*dx + dy*dy); + + return result; +} +#endif + +// Time measure returned are milliseconds +static double GetCurrentTime(void) +{ + double time = 0; + +#if defined(_WIN32) + unsigned long long int clockFrequency, currentTime; + + QueryPerformanceFrequency(&clockFrequency); // BE CAREFUL: Costly operation! + QueryPerformanceCounter(¤tTime); + + time = (double)currentTime/clockFrequency*1000.0f; // Time in miliseconds +#endif + +#if defined(__linux__) + // NOTE: Only for Linux-based systems + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + unsigned long long int nowTime = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; // Time in nanoseconds + + time = ((double)nowTime/1000000.0); // Time in miliseconds +#endif + +#if defined(__APPLE__) + //#define CLOCK_REALTIME CALENDAR_CLOCK // returns UTC time since 1970-01-01 + //#define CLOCK_MONOTONIC SYSTEM_CLOCK // returns the time since boot time + + clock_serv_t cclock; + mach_timespec_t now; + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + + // NOTE: OS X does not have clock_gettime(), using clock_get_time() + clock_get_time(cclock, &now); + mach_port_deallocate(mach_task_self(), cclock); + unsigned long long int nowTime = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; // Time in nanoseconds + + time = ((double)nowTime/1000000.0); // Time in miliseconds +#endif + + return time; +} + +#endif // GESTURES_IMPLEMENTATION diff --git a/raylib_pi4_test/libraylib.a b/raylib_pi4_test/libraylib.a new file mode 100644 index 0000000..d8b3e9c Binary files /dev/null and b/raylib_pi4_test/libraylib.a differ diff --git a/raylib_pi4_test/models.c b/raylib_pi4_test/models.c new file mode 100644 index 0000000..feaadf8 --- /dev/null +++ b/raylib_pi4_test/models.c @@ -0,0 +1,4829 @@ +/********************************************************************************************** +* +* raylib.models - Basic functions to deal with 3d shapes and 3d models +* +* CONFIGURATION: +* +* #define SUPPORT_FILEFORMAT_OBJ +* #define SUPPORT_FILEFORMAT_MTL +* #define SUPPORT_FILEFORMAT_IQM +* #define SUPPORT_FILEFORMAT_GLTF +* Selected desired fileformats to be supported for model data loading. +* +* #define SUPPORT_MESH_GENERATION +* Support procedural mesh generation functions, uses external par_shapes.h library +* NOTE: Some generated meshes DO NOT include generated texture coordinates +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include "utils.h" // Required for: LoadFileData(), LoadFileText(), SaveFileText() + +#include // Required for: sprintf() +#include // Required for: malloc(), free() +#include // Required for: memcmp(), strlen() +#include // Required for: sinf(), cosf(), sqrtf(), fabsf() + +#if defined(_WIN32) + #include // Required for: _chdir() [Used in LoadOBJ()] + #define CHDIR _chdir +#else + #include // Required for: chdir() (POSIX) [Used in LoadOBJ()] + #define CHDIR chdir +#endif + +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 + +#if defined(SUPPORT_FILEFORMAT_OBJ) || defined(SUPPORT_FILEFORMAT_MTL) + #define TINYOBJ_MALLOC RL_MALLOC + #define TINYOBJ_CALLOC RL_CALLOC + #define TINYOBJ_REALLOC RL_REALLOC + #define TINYOBJ_FREE RL_FREE + + #define TINYOBJ_LOADER_C_IMPLEMENTATION + #include "external/tinyobj_loader_c.h" // OBJ/MTL file formats loading +#endif + +#if defined(SUPPORT_FILEFORMAT_GLTF) + #define CGLTF_MALLOC RL_MALLOC + #define CGLTF_FREE RL_FREE + + #define CGLTF_IMPLEMENTATION + #include "external/cgltf.h" // glTF file format loading + #include "external/stb_image.h" // glTF texture images loading +#endif + +#if defined(SUPPORT_MESH_GENERATION) + #define PAR_MALLOC(T, N) ((T*)RL_MALLOC(N*sizeof(T))) + #define PAR_CALLOC(T, N) ((T*)RL_CALLOC(N*sizeof(T), 1)) + #define PAR_REALLOC(T, BUF, N) ((T*)RL_REALLOC(BUF, sizeof(T)*(N))) + #define PAR_FREE RL_FREE + + #define PAR_SHAPES_IMPLEMENTATION + #include "external/par_shapes.h" // Shapes 3d parametric generation +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_OBJ) +static Model LoadOBJ(const char *fileName); // Load OBJ mesh data +#endif +#if defined(SUPPORT_FILEFORMAT_IQM) +static Model LoadIQM(const char *fileName); // Load IQM mesh data +static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCount); // Load IQM animation data +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) +static Model LoadGLTF(const char *fileName); // Load GLTF mesh data +static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data +static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex); +static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex); +static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex); +static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data); +static void InitGLTFBones(Model* model, const cgltf_data* data); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Draw a line in 3D world space +void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) +{ + // WARNING: Be careful with internal buffer vertex alignment + // when using RL_LINES or RL_TRIANGLES, data is aligned to fit + // lines-triangles-quads in the same indexed buffers!!! + rlCheckRenderBatchLimit(8); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(startPos.x, startPos.y, startPos.z); + rlVertex3f(endPos.x, endPos.y, endPos.z); + rlEnd(); +} + +// Draw a point in 3D space, actually a small line +void DrawPoint3D(Vector3 position, Color color) +{ + rlCheckRenderBatchLimit(8); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(0.0f, 0.0f, 0.0f); + rlVertex3f(0.0f, 0.0f, 0.1f); + rlEnd(); + rlPopMatrix(); +} + +// Draw a circle in 3D world space +void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color) +{ + rlCheckRenderBatchLimit(2*36); + + rlPushMatrix(); + rlTranslatef(center.x, center.y, center.z); + rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); + + rlBegin(RL_LINES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex3f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius, 0.0f); + rlVertex3f(sinf(DEG2RAD*(i + 10))*radius, cosf(DEG2RAD*(i + 10))*radius, 0.0f); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a color-filled triangle (vertex in counter-clockwise order!) +void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color) +{ + rlCheckRenderBatchLimit(3); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(v1.x, v1.y, v1.z); + rlVertex3f(v2.x, v2.y, v2.z); + rlVertex3f(v3.x, v3.y, v3.z); + rlEnd(); +} + +// Draw a triangle strip defined by points +void DrawTriangleStrip3D(Vector3 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + rlCheckRenderBatchLimit(3*(pointsCount - 2)); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 2; i < pointsCount; i++) + { + if ((i%2) == 0) + { + rlVertex3f(points[i].x, points[i].y, points[i].z); + rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z); + rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z); + } + else + { + rlVertex3f(points[i].x, points[i].y, points[i].z); + rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z); + rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z); + } + } + rlEnd(); + } +} + +// Draw cube +// NOTE: Cube position is the center position +void DrawCube(Vector3 position, float width, float height, float length, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlCheckRenderBatchLimit(36); + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + rlTranslatef(position.x, position.y, position.z); + //rlRotatef(45, 0, 1, 0); + //rlScalef(1.0f, 1.0f, 1.0f); // NOTE: Vertices are directly scaled on definition + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front face + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + + // Back face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + + // Top face + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + // Bottom face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + + rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + + // Right face + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + // Left face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right + + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlEnd(); + rlPopMatrix(); +} + +// Draw cube (Vector version) +void DrawCubeV(Vector3 position, Vector3 size, Color color) +{ + DrawCube(position, size.x, size.y, size.z, color); +} + +// Draw cube wires +void DrawCubeWires(Vector3 position, float width, float height, float length, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlCheckRenderBatchLimit(36); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front Face ----------------------------------------------------- + // Bottom Line + rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + + // Left Line + rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Right + + // Top Line + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Right + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + + // Right Line + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left + rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + + // Back Face ------------------------------------------------------ + // Bottom Line + rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom Left + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + + // Left Line + rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right + + // Top Line + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + + // Right Line + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left + rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom Left + + // Top Face ------------------------------------------------------- + // Left Line + rlVertex3f(x-width/2, y+height/2, z+length/2); // Top Left Front + rlVertex3f(x-width/2, y+height/2, z-length/2); // Top Left Back + + // Right Line + rlVertex3f(x+width/2, y+height/2, z+length/2); // Top Right Front + rlVertex3f(x+width/2, y+height/2, z-length/2); // Top Right Back + + // Bottom Face --------------------------------------------------- + // Left Line + rlVertex3f(x-width/2, y-height/2, z+length/2); // Top Left Front + rlVertex3f(x-width/2, y-height/2, z-length/2); // Top Left Back + + // Right Line + rlVertex3f(x+width/2, y-height/2, z+length/2); // Top Right Front + rlVertex3f(x+width/2, y-height/2, z-length/2); // Top Right Back + rlEnd(); + rlPopMatrix(); +} + +// Draw cube wires (vector version) +void DrawCubeWiresV(Vector3 position, Vector3 size, Color color) +{ + DrawCubeWires(position, size.x, size.y, size.z, color); +} + +// Draw cube +// NOTE: Cube position is the center position +void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color) +{ + float x = position.x; + float y = position.y; + float z = position.z; + + rlCheckRenderBatchLimit(36); + + rlSetTexture(texture.id); + + //rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + //rlTranslatef(2.0f, 0.0f, 0.0f); + //rlRotatef(45, 0, 1, 0); + //rlScalef(2.0f, 2.0f, 2.0f); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + // Front Face + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left Of The Texture and Quad + // Back Face + rlNormal3f(0.0f, 0.0f, - 1.0f); // Normal Pointing Away From Viewer + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Left Of The Texture and Quad + // Top Face + rlNormal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad + // Bottom Face + rlNormal3f(0.0f, - 1.0f, 0.0f); // Normal Pointing Down + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad + // Right face + rlNormal3f(1.0f, 0.0f, 0.0f); // Normal Pointing Right + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left Of The Texture and Quad + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left Of The Texture and Quad + // Left Face + rlNormal3f( - 1.0f, 0.0f, 0.0f); // Normal Pointing Left + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left Of The Texture and Quad + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Right Of The Texture and Quad + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad + rlEnd(); + //rlPopMatrix(); + + rlSetTexture(0); +} + +// Draw sphere +void DrawSphere(Vector3 centerPos, float radius, Color color) +{ + DrawSphereEx(centerPos, radius, 16, 16, color); +} + +// Draw sphere with extended parameters +void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color) +{ + int numVertex = (rings + 2)*slices*6; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(radius, radius, radius); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < (rings + 2); i++) + { + for (int j = 0; j < slices; j++) + { + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + } + } + rlEnd(); + rlPopMatrix(); +} + +// Draw sphere wires +void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color) +{ + int numVertex = (rings + 2)*slices*6; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(radius, radius, radius); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < (rings + 2); i++) + { + for (int j = 0; j < slices; j++) + { + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), + sinf(DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + } + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a cylinder +// NOTE: It could be also used for pyramid and cone +void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color) +{ + if (sides < 3) sides = 3; + + int numVertex = sides*6; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + if (radiusTop > 0) + { + // Draw Body ------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); //Bottom Right + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); //Top Right + + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); //Top Left + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); //Top Right + } + + // Draw Cap -------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(0, height, 0); + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + } + } + else + { + // Draw Cone ------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(0, height, 0); + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + } + } + + // Draw Base ----------------------------------------------------------------------------------------- + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(0, 0, 0); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a wired cylinder +// NOTE: It could be also used for pyramid and cone +void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color) +{ + if (sides < 3) sides = 3; + + int numVertex = sides*8; + rlCheckRenderBatchLimit(numVertex); + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < 360; i += 360/sides) + { + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + + rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); + + rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); + rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a plane +void DrawPlane(Vector3 centerPos, Vector2 size, Color color) +{ + rlCheckRenderBatchLimit(4); + + // NOTE: Plane is always created on XZ ground + rlPushMatrix(); + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(size.x, 1.0f, size.y); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + rlNormal3f(0.0f, 1.0f, 0.0f); + + rlVertex3f(-0.5f, 0.0f, -0.5f); + rlVertex3f(-0.5f, 0.0f, 0.5f); + rlVertex3f(0.5f, 0.0f, 0.5f); + rlVertex3f(0.5f, 0.0f, -0.5f); + rlEnd(); + rlPopMatrix(); +} + +// Draw a ray line +void DrawRay(Ray ray, Color color) +{ + float scale = 10000; + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex3f(ray.position.x, ray.position.y, ray.position.z); + rlVertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale); + rlEnd(); +} + +// Draw a grid centered at (0, 0, 0) +void DrawGrid(int slices, float spacing) +{ + int halfSlices = slices/2; + + rlCheckRenderBatchLimit((slices + 2)*4); + + rlBegin(RL_LINES); + for (int i = -halfSlices; i <= halfSlices; i++) + { + if (i == 0) + { + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + rlColor3f(0.5f, 0.5f, 0.5f); + } + else + { + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + rlColor3f(0.75f, 0.75f, 0.75f); + } + + rlVertex3f((float)i*spacing, 0.0f, (float)-halfSlices*spacing); + rlVertex3f((float)i*spacing, 0.0f, (float)halfSlices*spacing); + + rlVertex3f((float)-halfSlices*spacing, 0.0f, (float)i*spacing); + rlVertex3f((float)halfSlices*spacing, 0.0f, (float)i*spacing); + } + rlEnd(); +} + +// Load model from files (mesh and material) +Model LoadModel(const char *fileName) +{ + Model model = { 0 }; + +#if defined(SUPPORT_FILEFORMAT_OBJ) + if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf;.glb")) model = LoadGLTF(fileName); +#endif + + // Make sure model transform is set to identity matrix! + model.transform = MatrixIdentity(); + + if (model.meshCount == 0) + { + model.meshCount = 1; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); +#if defined(SUPPORT_MESH_GENERATION) + TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load mesh data, default to cube mesh", fileName); + model.meshes[0] = GenMeshCube(1.0f, 1.0f, 1.0f); +#else + TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load mesh data", fileName); +#endif + } + else + { + // Upload vertex data to GPU (static mesh) + for (int i = 0; i < model.meshCount; i++) UploadMesh(&model.meshes[i], false); + } + + if (model.materialCount == 0) + { + TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to load material data, default to white material", fileName); + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + if (model.meshMaterial == NULL) model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + } + + return model; +} + +// Load model from generated mesh +// WARNING: A shallow copy of mesh is generated, passed by value, +// as long as struct contains pointers to data and some values, we get a copy +// of mesh pointing to same data as original version... be careful! +Model LoadModelFromMesh(Mesh mesh) +{ + Model model = { 0 }; + + model.transform = MatrixIdentity(); + + model.meshCount = 1; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshes[0] = mesh; + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + model.meshMaterial[0] = 0; // First material index + + return model; +} + +// Unload model (meshes/materials) from memory (RAM and/or VRAM) +// NOTE: This function takes care of all model elements, for a detailed control +// over them, use UnloadMesh() and UnloadMaterial() +void UnloadModel(Model model) +{ + // Unload meshes + for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]); + + // Unload materials maps + // NOTE: As the user could be sharing shaders and textures between models, + // we don't unload the material but just free it's maps, + // the user is responsible for freeing models shaders and textures + for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps); + + // Unload arrays + RL_FREE(model.meshes); + RL_FREE(model.materials); + RL_FREE(model.meshMaterial); + + // Unload animation data + RL_FREE(model.bones); + RL_FREE(model.bindPose); + + TRACELOG(LOG_INFO, "MODEL: Unloaded model (and meshes) from RAM and VRAM"); +} + +// Unload model (but not meshes) from memory (RAM and/or VRAM) +void UnloadModelKeepMeshes(Model model) +{ + // Unload materials maps + // NOTE: As the user could be sharing shaders and textures between models, + // we don't unload the material but just free it's maps, + // the user is responsible for freeing models shaders and textures + for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps); + + // Unload arrays + RL_FREE(model.meshes); + RL_FREE(model.materials); + RL_FREE(model.meshMaterial); + + // Unload animation data + RL_FREE(model.bones); + RL_FREE(model.bindPose); + + TRACELOG(LOG_INFO, "MODEL: Unloaded model (but not meshes) from RAM and VRAM"); +} + +// Upload vertex data into a VAO (if supported) and VBO +void UploadMesh(Mesh *mesh, bool dynamic) +{ + if (mesh->vaoId > 0) + { + // Check if mesh has already been loaded in GPU + TRACELOG(LOG_WARNING, "VAO: [ID %i] Trying to re-load an already loaded mesh", mesh->vaoId); + return; + } + + mesh->vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); + + mesh->vaoId = 0; // Vertex Array Object + mesh->vboId[0] = 0; // Vertex buffer: positions + mesh->vboId[1] = 0; // Vertex buffer: texcoords + mesh->vboId[2] = 0; // Vertex buffer: normals + mesh->vboId[3] = 0; // Vertex buffer: colors + mesh->vboId[4] = 0; // Vertex buffer: tangents + mesh->vboId[5] = 0; // Vertex buffer: texcoords2 + mesh->vboId[6] = 0; // Vertex buffer: indices + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mesh->vaoId = rlLoadVertexArray(); + rlEnableVertexArray(mesh->vaoId); + + // NOTE: Attributes must be uploaded considering default locations points + + // Enable vertex attributes: position (shader-location = 0) + mesh->vboId[0] = rlLoadVertexBuffer(mesh->vertices, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(0, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(0); + + // Enable vertex attributes: texcoords (shader-location = 1) + mesh->vboId[1] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(1, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(1); + + if (mesh->normals != NULL) + { + // Enable vertex attributes: normals (shader-location = 2) + mesh->vboId[2] = rlLoadVertexBuffer(mesh->normals, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(2, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(2); + } + else + { + // Default color vertex attribute set to WHITE + float value[3] = { 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(2, value, SHADER_ATTRIB_VEC3, 3); + rlDisableVertexAttribute(2); + } + + if (mesh->colors != NULL) + { + // Enable vertex attribute: color (shader-location = 3) + mesh->vboId[3] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic); + rlSetVertexAttribute(3, 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(3); + } + else + { + // Default color vertex attribute set to WHITE + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(3, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(3); + } + + if (mesh->tangents != NULL) + { + // Enable vertex attribute: tangent (shader-location = 4) + mesh->vboId[4] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic); + rlSetVertexAttribute(4, 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(4); + } + else + { + // Default tangents vertex attribute + float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + rlSetVertexAttributeDefault(4, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(4); + } + + if (mesh->texcoords2 != NULL) + { + // Enable vertex attribute: texcoord2 (shader-location = 5) + mesh->vboId[5] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(5, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(5); + } + else + { + // Default texcoord2 vertex attribute + float value[2] = { 0.0f, 0.0f }; + rlSetVertexAttributeDefault(5, value, SHADER_ATTRIB_VEC2, 2); + rlDisableVertexAttribute(5); + } + + if (mesh->indices != NULL) + { + mesh->vboId[6] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic); + } + + if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); + else TRACELOG(LOG_INFO, "VBO: Mesh uploaded successfully to VRAM (GPU)"); + + rlDisableVertexArray(); +#endif +} + +// Update mesh vertex data in GPU for a specific buffer index +void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset) +{ + rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset); +} + +// Draw a 3d mesh with material and transform +void DrawMesh(Mesh mesh, Material material, Matrix transform) +{ + DrawMeshInstanced(mesh, material, &transform, 1); +} + +// Draw multiple mesh instances with material and different transforms +void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_VERTEX_ARRAY 0x8074 + #define GL_NORMAL_ARRAY 0x8075 + #define GL_COLOR_ARRAY 0x8076 + #define GL_TEXTURE_COORD_ARRAY 0x8078 + + rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); + + rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices); + rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords); + rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals); + rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors); + + rlPushMatrix(); + rlMultMatrixf(MatrixToFloat(transforms[0])); + rlColor4ub(material.maps[MATERIAL_MAP_DIFFUSE].color.r, + material.maps[MATERIAL_MAP_DIFFUSE].color.g, + material.maps[MATERIAL_MAP_DIFFUSE].color.b, + material.maps[MATERIAL_MAP_DIFFUSE].color.a); + + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, mesh.indices); + else rlDrawVertexArray(0, mesh.vertexCount); + rlPopMatrix(); + + rlDisableStatePointer(GL_VERTEX_ARRAY); + rlDisableStatePointer(GL_TEXTURE_COORD_ARRAY); + rlDisableStatePointer(GL_NORMAL_ARRAY); + rlDisableStatePointer(GL_COLOR_ARRAY); + + rlDisableTexture(); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check instancing + bool instancing = false; + if (instances < 1) return; + else if (instances > 1) instancing = true; + float16 *instanceTransforms = NULL; + unsigned int instancesVboId = 0; + + // Bind shader program + rlEnableShader(material.shader.id); + + // Send required data to shader (matrices, values) + //----------------------------------------------------- + // Upload to shader material.colDiffuse + if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1) + { + float values[4] = { + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1); + } + + // Upload to shader material.colSpecular (if location available) + if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1) + { + float values[4] = { + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.r/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.g/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.b/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1); + } + + // Get a copy of current matrices to work with, + // just in case stereo render is required and we need to modify them + // NOTE: At this point the modelview matrix just contains the view matrix (camera) + // That's because BeginMode3D() sets it and there is no model-drawing function + // that modifies it, all use rlPushMatrix() and rlPopMatrix() + Matrix matView = rlGetMatrixModelview(); + Matrix matModelView = matView; + Matrix matProjection = rlGetMatrixProjection(); + + // Upload view and projection matrices (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView); + if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection); + + if (instancing) + { + // Create instances buffer + instanceTransforms = RL_MALLOC(instances*sizeof(float16)); + + // Fill buffer with instances transformations as float16 arrays + for (int i = 0; i < instances; i++) instanceTransforms[i] = MatrixToFloatV(transforms[i]); + + // Enable mesh VAO to attach new buffer + rlEnableVertexArray(mesh.vaoId); + + // This could alternatively use a static VBO and either glMapBuffer() or glBufferSubData(). + // It isn't clear which would be reliably faster in all cases and on all platforms, + // anecdotally glMapBuffer() seems very slow (syncs) while glBufferSubData() seems + // no faster, since we're transferring all the transform matrices anyway + instancesVboId = rlLoadVertexBuffer(instanceTransforms, instances*sizeof(float16), false); + + // Instances transformation matrices are send to shader attribute location: SHADER_LOC_MATRIX_MODEL + for (unsigned int i = 0; i < 4; i++) + { + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 4, RL_FLOAT, 0, sizeof(Matrix), (void *)(i*sizeof(Vector4))); + rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 1); + } + + rlDisableVertexBuffer(); + rlDisableVertexArray(); + + // Accumulate internal matrix transform (push/pop) and view matrix + // NOTE: In this case, model instance transformation must be computed in the shader + matModelView = MatrixMultiply(rlGetMatrixTransform(), matView); + } + else + { + // Model transformation matrix is send to shader uniform location: SHADER_LOC_MATRIX_MODEL + if (material.shader.locs[SHADER_LOC_MATRIX_MODEL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MODEL], transforms[0]); + + // Accumulate several transformations: + // matView: rlgl internal modelview matrix (actually, just view matrix) + // rlGetMatrixTransform(): rlgl internal transform matrix due to push/pop matrix stack + // transform: function parameter transformation + matModelView = MatrixMultiply(transforms[0], MatrixMultiply(rlGetMatrixTransform(), matView)); + } + + // Upload model normal matrix (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModelView))); + //----------------------------------------------------- + + // Bind active texture maps (if available) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Enable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id); + else rlEnableTexture(material.maps[i].texture.id); + + rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1); + } + } + + // Try binding vertex array objects (VAO) + // or use VBOs if not possible + if (!rlEnableVertexArray(mesh.vaoId)) + { + // Bind mesh VBO data: vertex position (shader-location = 0) + rlEnableVertexBuffer(mesh.vboId[0]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + rlEnableVertexBuffer(mesh.vboId[0]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind mesh VBO data: vertex texcoords (shader-location = 1) + rlEnableVertexBuffer(mesh.vboId[1]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1) + { + // Bind mesh VBO data: vertex normals (shader-location = 2) + rlEnableVertexBuffer(mesh.vboId[2]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]); + } + + // Bind mesh VBO data: vertex colors (shader-location = 3, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1) + { + if (mesh.vboId[3] != 0) + { + rlEnableVertexBuffer(mesh.vboId[3]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + else + { + // Set default value for unused attribute + // NOTE: Required when using default shader and no VAO support + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC2, 4); + rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + } + + // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1) + { + rlEnableVertexBuffer(mesh.vboId[4]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]); + } + + // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1) + { + rlEnableVertexBuffer(mesh.vboId[5]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); + } + + if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]); + } + + int eyesCount = 1; + if (rlIsStereoRenderEnabled()) eyesCount = 2; + + for (int eye = 0; eye < eyesCount; eye++) + { + // Calculate model-view-projection matrix (MVP) + Matrix matMVP = MatrixIdentity(); + if (eyesCount == 1) matMVP = MatrixMultiply(matModelView, matProjection); + else + { + // Setup current eye viewport (half screen width) + rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight()); + matMVP = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye)); + } + + // Send combined model-view-projection matrix to shader + rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matMVP); + + if (instancing) // Draw mesh instanced + { + if (mesh.indices != NULL) rlDrawVertexArrayElementsInstanced(0, mesh.triangleCount*3, 0, instances); + else rlDrawVertexArrayInstanced(0, mesh.vertexCount, instances); + } + else // Draw mesh + { + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, 0); + else rlDrawVertexArray(0, mesh.vertexCount); + } + } + + // Unbind all binded texture maps + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Disable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); + else rlDisableTexture(); + } + + // Disable all possible vertex array objects (or VBOs) + rlDisableVertexArray(); + rlDisableVertexBuffer(); + rlDisableVertexBufferElement(); + + // Disable shader program + rlDisableShader(); + + if (instancing) + { + // Remove instance transforms buffer + rlUnloadVertexBuffer(instancesVboId); + RL_FREE(instanceTransforms); + } + else + { + // Restore rlgl internal modelview and projection matrices + rlSetMatrixModelview(matView); + rlSetMatrixProjection(matProjection); + } +#endif +} + +// Unload mesh from memory (RAM and VRAM) +void UnloadMesh(Mesh mesh) +{ + // Unload rlgl mesh vboId data + rlUnloadVertexArray(mesh.vaoId); + + for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]); + RL_FREE(mesh.vboId); + + RL_FREE(mesh.vertices); + RL_FREE(mesh.texcoords); + RL_FREE(mesh.normals); + RL_FREE(mesh.colors); + RL_FREE(mesh.tangents); + RL_FREE(mesh.texcoords2); + RL_FREE(mesh.indices); + + RL_FREE(mesh.animVertices); + RL_FREE(mesh.animNormals); + RL_FREE(mesh.boneWeights); + RL_FREE(mesh.boneIds); +} + +// Export mesh data to file +bool ExportMesh(Mesh mesh, const char *fileName) +{ + bool success = false; + + if (IsFileExtension(fileName, ".obj")) + { + // Estimated data size, it should be enough... + int dataSize = mesh.vertexCount/3* (int)strlen("v 0000.00f 0000.00f 0000.00f") + + mesh.vertexCount/2* (int)strlen("vt 0.000f 0.00f") + + mesh.vertexCount/3* (int)strlen("vn 0.000f 0.00f 0.00f") + + mesh.triangleCount/3* (int)strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000"); + + // NOTE: Text data buffer size is estimated considering mesh data size + char *txtData = (char *)RL_CALLOC(dataSize + 2000, sizeof(char)); + + int bytesCount = 0; + bytesCount += sprintf(txtData + bytesCount, "# //////////////////////////////////////////////////////////////////////////////////\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // feedback and support: ray[at]raylib.com //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + bytesCount += sprintf(txtData + bytesCount, "# // //\n"); + bytesCount += sprintf(txtData + bytesCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n"); + bytesCount += sprintf(txtData + bytesCount, "# Vertex Count: %i\n", mesh.vertexCount); + bytesCount += sprintf(txtData + bytesCount, "# Triangle Count: %i\n\n", mesh.triangleCount); + + bytesCount += sprintf(txtData + bytesCount, "g mesh\n"); + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) + { + bytesCount += sprintf(txtData + bytesCount, "v %.2f %.2f %.2f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]); + } + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2) + { + bytesCount += sprintf(txtData + bytesCount, "vt %.3f %.3f\n", mesh.texcoords[v], mesh.texcoords[v + 1]); + } + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) + { + bytesCount += sprintf(txtData + bytesCount, "vn %.3f %.3f %.3f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]); + } + + for (int i = 0; i < mesh.triangleCount; i += 3) + { + bytesCount += sprintf(txtData + bytesCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", i, i, i, i + 1, i + 1, i + 1, i + 2, i + 2, i + 2); + } + + bytesCount += sprintf(txtData + bytesCount, "\n"); + + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + } + else if (IsFileExtension(fileName, ".raw")) + { + // TODO: Support additional file formats to export mesh vertex data + } + + return success; +} + + +// Load materials from model file +Material *LoadMaterials(const char *fileName, int *materialCount) +{ + Material *materials = NULL; + unsigned int count = 0; + + // TODO: Support IQM and GLTF for materials parsing + +#if defined(SUPPORT_FILEFORMAT_MTL) + if (IsFileExtension(fileName, ".mtl")) + { + tinyobj_material_t *mats = NULL; + + int result = tinyobj_parse_mtl_file(&mats, &count, fileName); + if (result != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName); + + // TODO: Process materials to return + + tinyobj_materials_free(mats, count); + } +#else + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load material file", fileName); +#endif + + // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL) + if (materials != NULL) + { + for (unsigned int i = 0; i < count; i++) materials[i].shader = rlGetShaderDefault(); + } + + *materialCount = count; + return materials; +} + +// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +Material LoadMaterialDefault(void) +{ + Material material = { 0 }; + material.maps = (MaterialMap *)RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap)); + + material.shader = rlGetShaderDefault(); + material.maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // White texture (1x1 pixel) + //material.maps[MATERIAL_MAP_NORMAL].texture; // NOTE: By default, not set + //material.maps[MATERIAL_MAP_SPECULAR].texture; // NOTE: By default, not set + + material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; // Diffuse color + material.maps[MATERIAL_MAP_SPECULAR].color = WHITE; // Specular color + + return material; +} + +// Unload material from memory +void UnloadMaterial(Material material) +{ + // Unload material shader (avoid unloading default shader, managed by raylib) + if (material.shader.id != rlGetShaderDefault().id) UnloadShader(material.shader); + + // Unload loaded texture maps (avoid unloading default texture, managed by raylib) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id != rlGetTextureDefault().id) rlUnloadTexture(material.maps[i].texture.id); + } + + RL_FREE(material.maps); +} + +// Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +// NOTE: Previous texture should be manually unloaded +void SetMaterialTexture(Material *material, int mapType, Texture2D texture) +{ + material->maps[mapType].texture = texture; +} + +// Set the material for a mesh +void SetModelMeshMaterial(Model *model, int meshId, int materialId) +{ + if (meshId >= model->meshCount) TRACELOG(LOG_WARNING, "MESH: Id greater than mesh count"); + else if (materialId >= model->materialCount) TRACELOG(LOG_WARNING, "MATERIAL: Id greater than material count"); + else model->meshMaterial[meshId] = materialId; +} + +// Load model animations from file +ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount) +{ + ModelAnimation *animations = NULL; + +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) animations = LoadIQMModelAnimations(fileName, animCount); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadGLTFModelAnimations(fileName, animCount); +#endif + + return animations; +} + +// Update model animated vertex data (positions and normals) for a given frame +// NOTE: Updated data is uploaded to GPU +void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) +{ + if ((anim.frameCount > 0) && (anim.bones != NULL) && (anim.framePoses != NULL)) + { + if (frame >= anim.frameCount) frame = frame%anim.frameCount; + + for (int m = 0; m < model.meshCount; m++) + { + Vector3 animVertex = { 0 }; + Vector3 animNormal = { 0 }; + + Vector3 inTranslation = { 0 }; + Quaternion inRotation = { 0 }; + //Vector3 inScale = { 0 }; // Not used... + + Vector3 outTranslation = { 0 }; + Quaternion outRotation = { 0 }; + Vector3 outScale = { 0 }; + + int vCounter = 0; + int boneCounter = 0; + int boneId = 0; + float boneWeight = 0.0; + + for (int i = 0; i < model.meshes[m].vertexCount; i++) + { + model.meshes[m].animVertices[vCounter] = 0; + model.meshes[m].animVertices[vCounter + 1] = 0; + model.meshes[m].animVertices[vCounter + 2] = 0; + + model.meshes[m].animNormals[vCounter] = 0; + model.meshes[m].animNormals[vCounter + 1] = 0; + model.meshes[m].animNormals[vCounter + 2] = 0; + + for (int j = 0; j < 4; j++) + { + boneId = model.meshes[m].boneIds[boneCounter]; + boneWeight = model.meshes[m].boneWeights[boneCounter]; + inTranslation = model.bindPose[boneId].translation; + inRotation = model.bindPose[boneId].rotation; + //inScale = model.bindPose[boneId].scale; + outTranslation = anim.framePoses[frame][boneId].translation; + outRotation = anim.framePoses[frame][boneId].rotation; + outScale = anim.framePoses[frame][boneId].scale; + + // Vertices processing + // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position) + animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] }; + animVertex = Vector3Multiply(animVertex, outScale); + animVertex = Vector3Subtract(animVertex, inTranslation); + animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + animVertex = Vector3Add(animVertex, outTranslation); + model.meshes[m].animVertices[vCounter] += animVertex.x * boneWeight; + model.meshes[m].animVertices[vCounter + 1] += animVertex.y * boneWeight; + model.meshes[m].animVertices[vCounter + 2] += animVertex.z * boneWeight; + + // Normals processing + // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) + if (model.meshes[m].normals != NULL) + { + animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] }; + animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + model.meshes[m].animNormals[vCounter] += animNormal.x * boneWeight; + model.meshes[m].animNormals[vCounter + 1] += animNormal.y * boneWeight; + model.meshes[m].animNormals[vCounter + 2] += animNormal.z * boneWeight; + } + boneCounter += 1; + } + vCounter += 3; + } + + // Upload new vertex data to GPU for model drawing + rlUpdateVertexBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex position + rlUpdateVertexBuffer(model.meshes[m].vboId[2], model.meshes[m].animNormals, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex normals + } + } +} + +// Unload animation array data +void UnloadModelAnimations(ModelAnimation* animations, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) UnloadModelAnimation(animations[i]); + RL_FREE(animations); +} + +// Unload animation data +void UnloadModelAnimation(ModelAnimation anim) +{ + for (int i = 0; i < anim.frameCount; i++) RL_FREE(anim.framePoses[i]); + + RL_FREE(anim.bones); + RL_FREE(anim.framePoses); +} + +// Check model animation skeleton match +// NOTE: Only number of bones and parent connections are checked +bool IsModelAnimationValid(Model model, ModelAnimation anim) +{ + int result = true; + + if (model.boneCount != anim.boneCount) result = false; + else + { + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; } + } + } + + return result; +} + +#if defined(SUPPORT_MESH_GENERATION) +// Generate polygonal mesh +Mesh GenMeshPoly(int sides, float radius) +{ + Mesh mesh = { 0 }; + + if (sides < 3) return mesh; + + int vertexCount = sides*3; + + // Vertices definition + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + + float d = 0.0f, dStep = 360.0f/sides; + for (int v = 0; v < vertexCount; v += 3) + { + vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f }; + vertices[v + 1] = (Vector3){ sinf(DEG2RAD*d)*radius, 0.0f, cosf(DEG2RAD*d)*radius }; + vertices[v + 2] = (Vector3){sinf(DEG2RAD*(d+dStep))*radius, 0.0f, cosf(DEG2RAD*(d+dStep))*radius }; + d += dStep; + } + + // Normals definition + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); + for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f }; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = sides; + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + + // Upload vertex data to GPU (static mesh) + // NOTE: mesh.vboId array is allocated inside UploadMesh() + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate plane mesh (with subdivisions) +Mesh GenMeshPlane(float width, float length, int resX, int resZ) +{ + Mesh mesh = { 0 }; + +#define CUSTOM_MESH_GEN_PLANE +#if defined(CUSTOM_MESH_GEN_PLANE) + resX++; + resZ++; + + // Vertices definition + int vertexCount = resX*resZ; // vertices get reused for the faces + + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int z = 0; z < resZ; z++) + { + // [-length/2, length/2] + float zPos = ((float)z/(resZ - 1) - 0.5f)*length; + for (int x = 0; x < resX; x++) + { + // [-width/2, width/2] + float xPos = ((float)x/(resX - 1) - 0.5f)*width; + vertices[x + z*resX] = (Vector3){ xPos, 0.0f, zPos }; + } + } + + // Normals definition + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); + for (int v = 0; v < resZ; v++) + { + for (int u = 0; u < resX; u++) + { + texcoords[u + v*resX] = (Vector2){ (float)u/(resX - 1), (float)v/(resZ - 1) }; + } + } + + // Triangles definition (indices) + int numFaces = (resX - 1)*(resZ - 1); + int *triangles = (int *)RL_MALLOC(numFaces*6*sizeof(int)); + int t = 0; + for (int face = 0; face < numFaces; face++) + { + // Retrieve lower left corner from face ind + int i = face % (resX - 1) + (face/(resZ - 1)*resX); + + triangles[t++] = i + resX; + triangles[t++] = i + 1; + triangles[t++] = i; + + triangles[t++] = i + resX; + triangles[t++] = i + resX + 1; + triangles[t++] = i + 1; + } + + mesh.vertexCount = vertexCount; + mesh.triangleCount = numFaces*2; + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.indices = (unsigned short *)RL_MALLOC(mesh.triangleCount*3*sizeof(unsigned short)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + // Mesh indices array initialization + for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i]; + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + RL_FREE(triangles); + +#else // Use par_shapes library to generate plane mesh + + par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!! + par_shapes_scale(plane, width, length, 1.0f); + par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_translate(plane, -width/2, 0.0f, length/2); + + mesh.vertices = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(plane->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = plane->ntriangles*3; + mesh.triangleCount = plane->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = plane->points[plane->triangles[k]*3]; + mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2]; + + mesh.normals[k*3] = plane->normals[plane->triangles[k]*3]; + mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(plane); +#endif + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generated cuboid mesh +Mesh GenMeshCube(float width, float height, float length) +{ + Mesh mesh = { 0 }; + +#define CUSTOM_MESH_GEN_CUBE +#if defined(CUSTOM_MESH_GEN_CUBE) + float vertices[] = { + -width/2, -height/2, length/2, + width/2, -height/2, length/2, + width/2, height/2, length/2, + -width/2, height/2, length/2, + -width/2, -height/2, -length/2, + -width/2, height/2, -length/2, + width/2, height/2, -length/2, + width/2, -height/2, -length/2, + -width/2, height/2, -length/2, + -width/2, height/2, length/2, + width/2, height/2, length/2, + width/2, height/2, -length/2, + -width/2, -height/2, -length/2, + width/2, -height/2, -length/2, + width/2, -height/2, length/2, + -width/2, -height/2, length/2, + width/2, -height/2, -length/2, + width/2, height/2, -length/2, + width/2, height/2, length/2, + width/2, -height/2, length/2, + -width/2, -height/2, -length/2, + -width/2, -height/2, length/2, + -width/2, height/2, length/2, + -width/2, height/2, -length/2 + }; + + float texcoords[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + float normals[] = { + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f + }; + + mesh.vertices = (float *)RL_MALLOC(24*3*sizeof(float)); + memcpy(mesh.vertices, vertices, 24*3*sizeof(float)); + + mesh.texcoords = (float *)RL_MALLOC(24*2*sizeof(float)); + memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float)); + + mesh.normals = (float *)RL_MALLOC(24*3*sizeof(float)); + memcpy(mesh.normals, normals, 24*3*sizeof(float)); + + mesh.indices = (unsigned short *)RL_MALLOC(36*sizeof(unsigned short)); + + int k = 0; + + // Indices can be initialized right now + for (int i = 0; i < 36; i+=6) + { + mesh.indices[i] = 4*k; + mesh.indices[i+1] = 4*k+1; + mesh.indices[i+2] = 4*k+2; + mesh.indices[i+3] = 4*k; + mesh.indices[i+4] = 4*k+2; + mesh.indices[i+5] = 4*k+3; + + k++; + } + + mesh.vertexCount = 24; + mesh.triangleCount = 12; + +#else // Use par_shapes library to generate cube mesh +/* +// Platonic solids: +par_shapes_mesh* par_shapes_create_tetrahedron(); // 4 sides polyhedron (pyramid) +par_shapes_mesh* par_shapes_create_cube(); // 6 sides polyhedron (cube) +par_shapes_mesh* par_shapes_create_octahedron(); // 8 sides polyhedron (dyamond) +par_shapes_mesh* par_shapes_create_dodecahedron(); // 12 sides polyhedron +par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron +*/ + // Platonic solid generation: cube (6 sides) + // NOTE: No normals/texcoords generated by default + par_shapes_mesh *cube = par_shapes_create_cube(); + cube->tcoords = PAR_MALLOC(float, 2*cube->npoints); + for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; + par_shapes_scale(cube, width, height, length); + par_shapes_translate(cube, -width/2, 0.0f, -length/2); + par_shapes_compute_normals(cube); + + mesh.vertices = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cube->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cube->ntriangles*3; + mesh.triangleCount = cube->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cube->points[cube->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cube->normals[cube->triangles[k]*3]; + mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cube); +#endif + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate sphere mesh (standard sphere) +Mesh GenMeshSphere(float radius, int rings, int slices) +{ + Mesh mesh = { 0 }; + + if ((rings >= 3) && (slices >= 3)) + { + par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere"); + + return mesh; +} + +// Generate hemi-sphere mesh (half sphere, no bottom cap) +Mesh GenMeshHemiSphere(float radius, int rings, int slices) +{ + Mesh mesh = { 0 }; + + if ((rings >= 3) && (slices >= 3)) + { + if (radius < 0.0f) radius = 0.0f; + + par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere"); + + return mesh; +} + +// Generate cylinder mesh +Mesh GenMeshCylinder(float radius, float height, int slices) +{ + Mesh mesh = { 0 }; + + if (slices >= 3) + { + // Instance a cylinder that sits on the Z=0 plane using the given tessellation + // levels across the UV domain. Think of "slices" like a number of pizza + // slices, and "stacks" like a number of stacked rings. + // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale + par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); + par_shapes_scale(cylinder, radius, radius, height); + par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(cylinder, PI/2.0f, (float[]){ 0, 1, 0 }); + + // Generate an orientable disk shape (top cap) + par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 }); + capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints); + for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; + par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_translate(capTop, 0, height, 0); + + // Generate an orientable disk shape (bottom cap) + par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 }); + capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); + for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; + par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); + + par_shapes_merge_and_free(cylinder, capTop); + par_shapes_merge_and_free(cylinder, capBottom); + + mesh.vertices = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cylinder->ntriangles*3; + mesh.triangleCount = cylinder->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3]; + mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cylinder); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder"); + + return mesh; +} + +// Generate torus mesh +Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) +{ + Mesh mesh = { 0 }; + + if ((sides >= 3) && (radSeg >= 3)) + { + if (radius > 1.0f) radius = 1.0f; + else if (radius < 0.1f) radius = 0.1f; + + // Create a donut that sits on the Z=0 plane with the specified inner radius + // The outer radius can be controlled with par_shapes_scale + par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius); + par_shapes_scale(torus, size/2, size/2, size/2); + + mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = torus->ntriangles*3; + mesh.triangleCount = torus->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = torus->points[torus->triangles[k]*3]; + mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2]; + + mesh.normals[k*3] = torus->normals[torus->triangles[k]*3]; + mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(torus); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus"); + + return mesh; +} + +// Generate trefoil knot mesh +Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) +{ + Mesh mesh = { 0 }; + + if ((sides >= 3) && (radSeg >= 3)) + { + if (radius > 3.0f) radius = 3.0f; + else if (radius < 0.5f) radius = 0.5f; + + par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius); + par_shapes_scale(knot, size, size, size); + + mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = knot->ntriangles*3; + mesh.triangleCount = knot->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = knot->points[knot->triangles[k]*3]; + mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2]; + + mesh.normals[k*3] = knot->normals[knot->triangles[k]*3]; + mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(knot); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot"); + + return mesh; +} + +// Generate a mesh from heightmap +// NOTE: Vertex data is uploaded to GPU +Mesh GenMeshHeightmap(Image heightmap, Vector3 size) +{ + #define GRAY_VALUE(c) ((c.r+c.g+c.b)/3) + + Mesh mesh = { 0 }; + + int mapX = heightmap.width; + int mapZ = heightmap.height; + + Color *pixels = LoadImageColors(heightmap); + + // NOTE: One vertex per pixel + mesh.triangleCount = (mapX-1)*(mapZ-1)*2; // One quad every four pixels + + mesh.vertexCount = mesh.triangleCount*3; + + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.colors = NULL; + + int vCounter = 0; // Used to count vertices float by float + int tcCounter = 0; // Used to count texcoords float by float + int nCounter = 0; // Used to count normals float by float + + int trisCounter = 0; + + Vector3 scaleFactor = { size.x/mapX, size.y/255.0f, size.z/mapZ }; + + Vector3 vA; + Vector3 vB; + Vector3 vC; + Vector3 vN; + + for (int z = 0; z < mapZ-1; z++) + { + for (int x = 0; x < mapX-1; x++) + { + // Fill vertices array with data + //---------------------------------------------------------- + + // one triangle - 3 vertex + mesh.vertices[vCounter] = (float)x*scaleFactor.x; + mesh.vertices[vCounter + 1] = (float)GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 2] = (float)z*scaleFactor.z; + + mesh.vertices[vCounter + 3] = (float)x*scaleFactor.x; + mesh.vertices[vCounter + 4] = (float)GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 5] = (float)(z + 1)*scaleFactor.z; + + mesh.vertices[vCounter + 6] = (float)(x + 1)*scaleFactor.x; + mesh.vertices[vCounter + 7] = (float)GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 8] = (float)z*scaleFactor.z; + + // another triangle - 3 vertex + mesh.vertices[vCounter + 9] = mesh.vertices[vCounter + 6]; + mesh.vertices[vCounter + 10] = mesh.vertices[vCounter + 7]; + mesh.vertices[vCounter + 11] = mesh.vertices[vCounter + 8]; + + mesh.vertices[vCounter + 12] = mesh.vertices[vCounter + 3]; + mesh.vertices[vCounter + 13] = mesh.vertices[vCounter + 4]; + mesh.vertices[vCounter + 14] = mesh.vertices[vCounter + 5]; + + mesh.vertices[vCounter + 15] = (float)(x + 1)*scaleFactor.x; + mesh.vertices[vCounter + 16] = (float)GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 17] = (float)(z + 1)*scaleFactor.z; + vCounter += 18; // 6 vertex, 18 floats + + // Fill texcoords array with data + //-------------------------------------------------------------- + mesh.texcoords[tcCounter] = (float)x/(mapX - 1); + mesh.texcoords[tcCounter + 1] = (float)z/(mapZ - 1); + + mesh.texcoords[tcCounter + 2] = (float)x/(mapX - 1); + mesh.texcoords[tcCounter + 3] = (float)(z + 1)/(mapZ - 1); + + mesh.texcoords[tcCounter + 4] = (float)(x + 1)/(mapX - 1); + mesh.texcoords[tcCounter + 5] = (float)z/(mapZ - 1); + + mesh.texcoords[tcCounter + 6] = mesh.texcoords[tcCounter + 4]; + mesh.texcoords[tcCounter + 7] = mesh.texcoords[tcCounter + 5]; + + mesh.texcoords[tcCounter + 8] = mesh.texcoords[tcCounter + 2]; + mesh.texcoords[tcCounter + 9] = mesh.texcoords[tcCounter + 3]; + + mesh.texcoords[tcCounter + 10] = (float)(x + 1)/(mapX - 1); + mesh.texcoords[tcCounter + 11] = (float)(z + 1)/(mapZ - 1); + tcCounter += 12; // 6 texcoords, 12 floats + + // Fill normals array with data + //-------------------------------------------------------------- + for (int i = 0; i < 18; i += 9) + { + vA.x = mesh.vertices[nCounter + i]; + vA.y = mesh.vertices[nCounter + i + 1]; + vA.z = mesh.vertices[nCounter + i + 2]; + + vB.x = mesh.vertices[nCounter + i + 3]; + vB.y = mesh.vertices[nCounter + i + 4]; + vB.z = mesh.vertices[nCounter + i + 5]; + + vC.x = mesh.vertices[nCounter + i + 6]; + vC.y = mesh.vertices[nCounter + i + 7]; + vC.z = mesh.vertices[nCounter + i + 8]; + + vN = Vector3Normalize(Vector3CrossProduct(Vector3Subtract(vB, vA), Vector3Subtract(vC, vA))); + + mesh.normals[nCounter + i] = vN.x; + mesh.normals[nCounter + i + 1] = vN.y; + mesh.normals[nCounter + i + 2] = vN.z; + + mesh.normals[nCounter + i + 3] = vN.x; + mesh.normals[nCounter + i + 4] = vN.y; + mesh.normals[nCounter + i + 5] = vN.z; + + mesh.normals[nCounter + i + 6] = vN.x; + mesh.normals[nCounter + i + 7] = vN.y; + mesh.normals[nCounter + i + 8] = vN.z; + } + + nCounter += 18; // 6 vertex, 18 floats + trisCounter += 2; + } + } + + UnloadImageColors(pixels); // Unload pixels color data + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate a cubes mesh from pixel data +// NOTE: Vertex data is uploaded to GPU +Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) +{ + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + Mesh mesh = { 0 }; + + Color *pixels = LoadImageColors(cubicmap); + + int mapWidth = cubicmap.width; + int mapHeight = cubicmap.height; + + // NOTE: Max possible number of triangles numCubes*(12 triangles by cube) + int maxTriangles = cubicmap.width*cubicmap.height*12; + + int vCounter = 0; // Used to count vertices + int tcCounter = 0; // Used to count texcoords + int nCounter = 0; // Used to count normals + + float w = cubeSize.x; + float h = cubeSize.z; + float h2 = cubeSize.y; + + Vector3 *mapVertices = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); + Vector2 *mapTexcoords = (Vector2 *)RL_MALLOC(maxTriangles*3*sizeof(Vector2)); + Vector3 *mapNormals = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); + + // Define the 6 normals of the cube, we will combine them accordingly later... + Vector3 n1 = { 1.0f, 0.0f, 0.0f }; + Vector3 n2 = { -1.0f, 0.0f, 0.0f }; + Vector3 n3 = { 0.0f, 1.0f, 0.0f }; + Vector3 n4 = { 0.0f, -1.0f, 0.0f }; + Vector3 n5 = { 0.0f, 0.0f, -1.0f }; + Vector3 n6 = { 0.0f, 0.0f, 1.0f }; + + // NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6) + typedef struct RectangleF { + float x; + float y; + float width; + float height; + } RectangleF; + + RectangleF rightTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; + RectangleF leftTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; + RectangleF frontTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; + RectangleF backTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; + RectangleF topTexUV = { 0.0f, 0.5f, 0.5f, 0.5f }; + RectangleF bottomTexUV = { 0.5f, 0.5f, 0.5f, 0.5f }; + + for (int z = 0; z < mapHeight; ++z) + { + for (int x = 0; x < mapWidth; ++x) + { + // Define the 8 vertex of the cube, we will combine them accordingly later... + Vector3 v1 = { w*(x - 0.5f), h2, h*(z - 0.5f) }; + Vector3 v2 = { w*(x - 0.5f), h2, h*(z + 0.5f) }; + Vector3 v3 = { w*(x + 0.5f), h2, h*(z + 0.5f) }; + Vector3 v4 = { w*(x + 0.5f), h2, h*(z - 0.5f) }; + Vector3 v5 = { w*(x + 0.5f), 0, h*(z - 0.5f) }; + Vector3 v6 = { w*(x - 0.5f), 0, h*(z - 0.5f) }; + Vector3 v7 = { w*(x - 0.5f), 0, h*(z + 0.5f) }; + Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) }; + + // We check pixel color to be WHITE -> draw full cube + if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE)) + { + // Define triangles and checking collateral cubes + //------------------------------------------------ + + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + // WARNING: Not required for a WHITE cubes, created to allow seeing the map from outside + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v2; + mapVertices[vCounter + 2] = v3; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v3; + mapVertices[vCounter + 5] = v4; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v8; + mapVertices[vCounter + 2] = v7; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v5; + mapVertices[vCounter + 5] = v8; + vCounter += 6; + + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + tcCounter += 6; + + // Checking cube on bottom of current cube + if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1)) + { + // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v2; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v3; + mapVertices[vCounter + 3] = v3; + mapVertices[vCounter + 4] = v7; + mapVertices[vCounter + 5] = v8; + vCounter += 6; + + mapNormals[nCounter] = n6; + mapNormals[nCounter + 1] = n6; + mapNormals[nCounter + 2] = n6; + mapNormals[nCounter + 3] = n6; + mapNormals[nCounter + 4] = n6; + mapNormals[nCounter + 5] = n6; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ frontTexUV.x, frontTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height }; + tcCounter += 6; + } + + // Checking cube on top of current cube + if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0)) + { + // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v5; + mapVertices[vCounter + 2] = v6; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n5; + mapNormals[nCounter + 1] = n5; + mapNormals[nCounter + 2] = n5; + mapNormals[nCounter + 3] = n5; + mapNormals[nCounter + 4] = n5; + mapNormals[nCounter + 5] = n5; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ backTexUV.x, backTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + tcCounter += 6; + } + + // Checking cube on right of current cube + if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1)) + { + // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v3; + mapVertices[vCounter + 1] = v8; + mapVertices[vCounter + 2] = v4; + mapVertices[vCounter + 3] = v4; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n1; + mapNormals[nCounter + 1] = n1; + mapNormals[nCounter + 2] = n1; + mapNormals[nCounter + 3] = n1; + mapNormals[nCounter + 4] = n1; + mapNormals[nCounter + 5] = n1; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ rightTexUV.x, rightTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height }; + tcCounter += 6; + } + + // Checking cube on left of current cube + if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0)) + { + // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v6; + mapVertices[vCounter + 5] = v7; + vCounter += 6; + + mapNormals[nCounter] = n2; + mapNormals[nCounter + 1] = n2; + mapNormals[nCounter + 2] = n2; + mapNormals[nCounter + 3] = n2; + mapNormals[nCounter + 4] = n2; + mapNormals[nCounter + 5] = n2; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + tcCounter += 6; + } + } + // We check pixel color to be BLACK, we will only draw floor and roof + else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK)) + { + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v3; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v3; + vCounter += 6; + + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v8; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + tcCounter += 6; + } + } + } + + // Move data from mapVertices temp arays to vertices float array + mesh.vertexCount = vCounter; + mesh.triangleCount = vCounter/3; + + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.colors = NULL; + + int fCounter = 0; + + // Move vertices data + for (int i = 0; i < vCounter; i++) + { + mesh.vertices[fCounter] = mapVertices[i].x; + mesh.vertices[fCounter + 1] = mapVertices[i].y; + mesh.vertices[fCounter + 2] = mapVertices[i].z; + fCounter += 3; + } + + fCounter = 0; + + // Move normals data + for (int i = 0; i < nCounter; i++) + { + mesh.normals[fCounter] = mapNormals[i].x; + mesh.normals[fCounter + 1] = mapNormals[i].y; + mesh.normals[fCounter + 2] = mapNormals[i].z; + fCounter += 3; + } + + fCounter = 0; + + // Move texcoords data + for (int i = 0; i < tcCounter; i++) + { + mesh.texcoords[fCounter] = mapTexcoords[i].x; + mesh.texcoords[fCounter + 1] = mapTexcoords[i].y; + fCounter += 2; + } + + RL_FREE(mapVertices); + RL_FREE(mapNormals); + RL_FREE(mapTexcoords); + + UnloadImageColors(pixels); // Unload pixels color data + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} +#endif // SUPPORT_MESH_GENERATION + +// Compute mesh bounding box limits +// NOTE: minVertex and maxVertex should be transformed by model transform matrix +BoundingBox MeshBoundingBox(Mesh mesh) +{ + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = { 0 }; + Vector3 maxVertex = { 0 }; + + if (mesh.vertices != NULL) + { + minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + + for (int i = 1; i < mesh.vertexCount; i++) + { + minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + } + } + + // Create the bounding box + BoundingBox box = { 0 }; + box.min = minVertex; + box.max = maxVertex; + + return box; +} + +// Compute mesh tangents +// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates +// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html +void MeshTangents(Mesh *mesh) +{ + if (mesh->tangents == NULL) mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float)); + else TRACELOG(LOG_WARNING, "MESH: Tangents data already available, re-writting"); + + Vector3 *tan1 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3)); + Vector3 *tan2 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3)); + + for (int i = 0; i < mesh->vertexCount; i += 3) + { + // Get triangle vertices + Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] }; + Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] }; + Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] }; + + // Get triangle texcoords + Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] }; + Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] }; + Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] }; + + float x1 = v2.x - v1.x; + float y1 = v2.y - v1.y; + float z1 = v2.z - v1.z; + float x2 = v3.x - v1.x; + float y2 = v3.y - v1.y; + float z2 = v3.z - v1.z; + + float s1 = uv2.x - uv1.x; + float t1 = uv2.y - uv1.y; + float s2 = uv3.x - uv1.x; + float t2 = uv3.y - uv1.y; + + float div = s1*t2 - s2*t1; + float r = (div == 0.0f)? 0.0f : 1.0f/div; + + Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r }; + Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r }; + + tan1[i + 0] = sdir; + tan1[i + 1] = sdir; + tan1[i + 2] = sdir; + + tan2[i + 0] = tdir; + tan2[i + 1] = tdir; + tan2[i + 2] = tdir; + } + + // Compute tangents considering normals + for (int i = 0; i < mesh->vertexCount; ++i) + { + Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + Vector3 tangent = tan1[i]; + + // TODO: Review, not sure if tangent computation is right, just used reference proposed maths... + #if defined(COMPUTE_TANGENTS_METHOD_01) + Vector3 tmp = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent))); + tmp = Vector3Normalize(tmp); + mesh->tangents[i*4 + 0] = tmp.x; + mesh->tangents[i*4 + 1] = tmp.y; + mesh->tangents[i*4 + 2] = tmp.z; + mesh->tangents[i*4 + 3] = 1.0f; + #else + Vector3OrthoNormalize(&normal, &tangent); + mesh->tangents[i*4 + 0] = tangent.x; + mesh->tangents[i*4 + 1] = tangent.y; + mesh->tangents[i*4 + 2] = tangent.z; + mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f; + #endif + } + + RL_FREE(tan1); + RL_FREE(tan2); + + // Load a new tangent attributes buffer + mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false); + + TRACELOG(LOG_INFO, "MESH: Tangents data computed for provided mesh"); +} + +// Compute mesh binormals (aka bitangent) +void MeshBinormals(Mesh *mesh) +{ + for (int i = 0; i < mesh->vertexCount; i++) + { + //Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + //Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; + //Vector3 binormal = Vector3Scale(Vector3CrossProduct(normal, tangent), mesh->tangents[i*4 + 3]); + + // TODO: Register computed binormal in mesh->binormal? + } +} + +// Draw a model (with texture if set) +void DrawModel(Model model, Vector3 position, float scale, Color tint) +{ + Vector3 vScale = { scale, scale, scale }; + Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f }; + + DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint); +} + +// Draw a model with extended parameters +void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + // Calculate transformation matrix from function parameters + // Get transform matrix (rotation -> scale -> translation) + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); + Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); + + Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); + + // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform) + model.transform = MatrixMultiply(model.transform, matTransform); + + for (int i = 0; i < model.meshCount; i++) + { + Color color = model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color; + + Color colorTint = WHITE; + colorTint.r = (unsigned char)((((float)color.r/255.0)*((float)tint.r/255.0))*255.0f); + colorTint.g = (unsigned char)((((float)color.g/255.0)*((float)tint.g/255.0))*255.0f); + colorTint.b = (unsigned char)((((float)color.b/255.0)*((float)tint.b/255.0))*255.0f); + colorTint.a = (unsigned char)((((float)color.a/255.0)*((float)tint.a/255.0))*255.0f); + + model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = colorTint; + DrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform); + model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = color; + } +} + +// Draw a model wires (with texture if set) +void DrawModelWires(Model model, Vector3 position, float scale, Color tint) +{ + rlEnableWireMode(); + + DrawModel(model, position, scale, tint); + + rlDisableWireMode(); +} + +// Draw a model wires (with texture if set) with extended parameters +void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + rlEnableWireMode(); + + DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint); + + rlDisableWireMode(); +} + +// Draw a billboard +void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint) +{ + Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + + DrawBillboardRec(camera, texture, source, center, size, tint); +} + +// Draw a billboard (part of a texture defined by a rectangle) +void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 center, float size, Color tint) +{ + // NOTE: Billboard size will maintain source rectangle aspect ratio, size will represent billboard width + Vector2 sizeRatio = { size, size*(float)source.height/source.width }; + + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + Vector3 right = { matView.m0, matView.m4, matView.m8 }; + //Vector3 up = { matView.m1, matView.m5, matView.m9 }; + + // NOTE: Billboard locked on axis-Y + Vector3 up = { 0.0f, 1.0f, 0.0f }; +/* + a-------b + | | + | * | + | | + d-------c +*/ + right = Vector3Scale(right, sizeRatio.x/2); + up = Vector3Scale(up, sizeRatio.y/2); + + Vector3 p1 = Vector3Add(right, up); + Vector3 p2 = Vector3Subtract(right, up); + + Vector3 a = Vector3Subtract(center, p2); + Vector3 b = Vector3Add(center, p1); + Vector3 c = Vector3Add(center, p2); + Vector3 d = Vector3Subtract(center, p1); + + rlCheckRenderBatchLimit(4); + + rlSetTexture(texture.id); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + + // Bottom-left corner for texture and quad + rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height); + rlVertex3f(a.x, a.y, a.z); + + // Top-left corner for texture and quad + rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height); + rlVertex3f(d.x, d.y, d.z); + + // Top-right corner for texture and quad + rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height); + rlVertex3f(c.x, c.y, c.z); + + // Bottom-right corner for texture and quad + rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height); + rlVertex3f(b.x, b.y, b.z); + rlEnd(); + + rlSetTexture(0); +} + +// Draw a bounding box with wires +void DrawBoundingBox(BoundingBox box, Color color) +{ + Vector3 size; + + size.x = fabsf(box.max.x - box.min.x); + size.y = fabsf(box.max.y - box.min.y); + size.z = fabsf(box.max.z - box.min.z); + + Vector3 center = { box.min.x + size.x/2.0f, box.min.y + size.y/2.0f, box.min.z + size.z/2.0f }; + + DrawCubeWires(center, size.x, size.y, size.z, color); +} + +// Detect collision between two spheres +bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2) +{ + bool collision = false; + + // Simple way to check for collision, just checking distance between two points + // Unfortunately, sqrtf() is a costly operation, so we avoid it with following solution + /* + float dx = center1.x - center2.x; // X distance between centers + float dy = center1.y - center2.y; // Y distance between centers + float dz = center1.z - center2.z; // Z distance between centers + + float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers + + if (distance <= (radius1 + radius2)) collision = true; + */ + + // Check for distances squared to avoid sqrtf() + if (Vector3DotProduct(Vector3Subtract(center2, center1), Vector3Subtract(center2, center1)) <= (radius1 + radius2)*(radius1 + radius2)) collision = true; + + return collision; +} + +// Detect collision between two boxes +// NOTE: Boxes are defined by two points minimum and maximum +bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2) +{ + bool collision = true; + + if ((box1.max.x >= box2.min.x) && (box1.min.x <= box2.max.x)) + { + if ((box1.max.y < box2.min.y) || (box1.min.y > box2.max.y)) collision = false; + if ((box1.max.z < box2.min.z) || (box1.min.z > box2.max.z)) collision = false; + } + else collision = false; + + return collision; +} + +// Detect collision between box and sphere +bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius) +{ + bool collision = false; + + float dmin = 0; + + if (center.x < box.min.x) dmin += powf(center.x - box.min.x, 2); + else if (center.x > box.max.x) dmin += powf(center.x - box.max.x, 2); + + if (center.y < box.min.y) dmin += powf(center.y - box.min.y, 2); + else if (center.y > box.max.y) dmin += powf(center.y - box.max.y, 2); + + if (center.z < box.min.z) dmin += powf(center.z - box.min.z, 2); + else if (center.z > box.max.z) dmin += powf(center.z - box.max.z, 2); + + if (dmin <= (radius*radius)) collision = true; + + return collision; +} + +// Detect collision between ray and sphere +bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius) +{ + bool collision = false; + + Vector3 raySpherePos = Vector3Subtract(center, ray.position); + float distance = Vector3Length(raySpherePos); + float vector = Vector3DotProduct(raySpherePos, ray.direction); + float d = radius*radius - (distance*distance - vector*vector); + + if (d >= 0.0f) collision = true; + + return collision; +} + +// Detect collision between ray and sphere with extended parameters and collision point detection +bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint) +{ + bool collision = false; + + Vector3 raySpherePos = Vector3Subtract(center, ray.position); + float distance = Vector3Length(raySpherePos); + float vector = Vector3DotProduct(raySpherePos, ray.direction); + float d = radius*radius - (distance*distance - vector*vector); + + if (d >= 0.0f) collision = true; + + // Check if ray origin is inside the sphere to calculate the correct collision point + float collisionDistance = 0; + + if (distance < radius) collisionDistance = vector + sqrtf(d); + else collisionDistance = vector - sqrtf(d); + + // Calculate collision point + Vector3 cPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, collisionDistance)); + + collisionPoint->x = cPoint.x; + collisionPoint->y = cPoint.y; + collisionPoint->z = cPoint.z; + + return collision; +} + +// Detect collision between ray and bounding box +bool CheckCollisionRayBox(Ray ray, BoundingBox box) +{ + bool collision = false; + + float t[8]; + t[0] = (box.min.x - ray.position.x)/ray.direction.x; + t[1] = (box.max.x - ray.position.x)/ray.direction.x; + t[2] = (box.min.y - ray.position.y)/ray.direction.y; + t[3] = (box.max.y - ray.position.y)/ray.direction.y; + t[4] = (box.min.z - ray.position.z)/ray.direction.z; + t[5] = (box.max.z - ray.position.z)/ray.direction.z; + t[6] = (float)fmax(fmax(fmin(t[0], t[1]), fmin(t[2], t[3])), fmin(t[4], t[5])); + t[7] = (float)fmin(fmin(fmax(t[0], t[1]), fmax(t[2], t[3])), fmax(t[4], t[5])); + + collision = !(t[7] < 0 || t[6] > t[7]); + + return collision; +} +// Get collision info between ray and mesh +RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform) +{ + RayHitInfo result = { 0 }; + + // Check if mesh vertex data on CPU for testing + if (mesh.vertices != NULL) + { + int triangleCount = mesh.triangleCount; + + // Test against all triangles in mesh + for (int i = 0; i < triangleCount; i++) + { + Vector3 a, b, c; + Vector3* vertdata = (Vector3*)mesh.vertices; + + if (mesh.indices) + { + a = vertdata[mesh.indices[i*3 + 0]]; + b = vertdata[mesh.indices[i*3 + 1]]; + c = vertdata[mesh.indices[i*3 + 2]]; + } + else + { + a = vertdata[i*3 + 0]; + b = vertdata[i*3 + 1]; + c = vertdata[i*3 + 2]; + } + + a = Vector3Transform(a, transform); + b = Vector3Transform(b, transform); + c = Vector3Transform(c, transform); + + RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); + + if (triHitInfo.hit) + { + // Save the closest hit triangle + if ((!result.hit) || (result.distance > triHitInfo.distance)) result = triHitInfo; + } + } + } + return result; +} + +// Get collision info between ray and model +RayHitInfo GetCollisionRayModel(Ray ray, Model model) +{ + RayHitInfo result = { 0 }; + + for (int m = 0; m < model.meshCount; m++) + { + RayHitInfo meshHitInfo = GetCollisionRayMesh(ray, model.meshes[m], model.transform); + + if (meshHitInfo.hit) + { + // Save the closest hit mesh + if ((!result.hit) || (result.distance > meshHitInfo.distance)) result = meshHitInfo; + } + } + + return result; +} + +// Get collision info between ray and triangle +// NOTE: Based on https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm +RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) +{ + #define EPSILON 0.000001 // A small number + + Vector3 edge1, edge2; + Vector3 p, q, tv; + float det, invDet, u, v, t; + RayHitInfo result = {0}; + + // Find vectors for two edges sharing V1 + edge1 = Vector3Subtract(p2, p1); + edge2 = Vector3Subtract(p3, p1); + + // Begin calculating determinant - also used to calculate u parameter + p = Vector3CrossProduct(ray.direction, edge2); + + // If determinant is near zero, ray lies in plane of triangle or ray is parallel to plane of triangle + det = Vector3DotProduct(edge1, p); + + // Avoid culling! + if ((det > -EPSILON) && (det < EPSILON)) return result; + + invDet = 1.0f/det; + + // Calculate distance from V1 to ray origin + tv = Vector3Subtract(ray.position, p1); + + // Calculate u parameter and test bound + u = Vector3DotProduct(tv, p)*invDet; + + // The intersection lies outside of the triangle + if ((u < 0.0f) || (u > 1.0f)) return result; + + // Prepare to test v parameter + q = Vector3CrossProduct(tv, edge1); + + // Calculate V parameter and test bound + v = Vector3DotProduct(ray.direction, q)*invDet; + + // The intersection lies outside of the triangle + if ((v < 0.0f) || ((u + v) > 1.0f)) return result; + + t = Vector3DotProduct(edge2, q)*invDet; + + if (t > EPSILON) + { + // Ray hit, get hit point and normal + result.hit = true; + result.distance = t; + result.hit = true; + result.normal = Vector3Normalize(Vector3CrossProduct(edge1, edge2)); + result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, t)); + } + + return result; +} + +// Get collision info between ray and ground plane (Y-normal plane) +RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight) +{ + #define EPSILON 0.000001 // A small number + + RayHitInfo result = { 0 }; + + if (fabsf(ray.direction.y) > EPSILON) + { + float distance = (ray.position.y - groundHeight)/-ray.direction.y; + + if (distance >= 0.0) + { + result.hit = true; + result.distance = distance; + result.normal = (Vector3){ 0.0, 1.0, 0.0 }; + result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, distance)); + result.position.y = groundHeight; + } + } + + return result; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +#if defined(SUPPORT_FILEFORMAT_OBJ) +// Load OBJ mesh data +static Model LoadOBJ(const char *fileName) +{ + Model model = { 0 }; + + tinyobj_attrib_t attrib = { 0 }; + tinyobj_shape_t *meshes = NULL; + unsigned int meshCount = 0; + + tinyobj_material_t *materials = NULL; + unsigned int materialCount = 0; + + char *fileData = LoadFileText(fileName); + + if (fileData != NULL) + { + unsigned int dataSize = (unsigned int)strlen(fileData); + char currentDir[1024] = { 0 }; + strcpy(currentDir, GetWorkingDirectory()); + const char *workingDir = GetDirectoryPath(fileName); + if (CHDIR(workingDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + } + + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileData, dataSize, flags); + + if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName); + else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes / %i materials", fileName, meshCount, materialCount); + + model.meshCount = materialCount; + + // Init model materials array + if (materialCount > 0) + { + model.materialCount = materialCount; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + TraceLog(LOG_INFO, "MODEL: model has %i material meshes", materialCount); + } + else + { + model.meshCount = 1; + TraceLog(LOG_INFO, "MODEL: No materials, putting all meshes in a default material"); + } + + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + + // count the faces for each material + int *matFaces = RL_CALLOC(meshCount, sizeof(int)); + + for (unsigned int mi = 0; mi < meshCount; mi++) + { + for (unsigned int fi = 0; fi < meshes[mi].length; fi++) + { + int idx = attrib.material_ids[meshes[mi].face_offset + fi]; + if (idx == -1) idx = 0; // for no material face (which could be the whole model) + matFaces[idx]++; + } + } + + //-------------------------------------- + // create the material meshes + + // running counts / indexes for each material mesh as we are + // building them at the same time + int *vCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *vtCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *vnCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *faceCount = RL_CALLOC(model.meshCount, sizeof(int)); + + // allocate space for each of the material meshes + for (int mi = 0; mi < model.meshCount; mi++) + { + model.meshes[mi].vertexCount = matFaces[mi]*3; + model.meshes[mi].triangleCount = matFaces[mi]; + model.meshes[mi].vertices = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float)); + model.meshes[mi].texcoords = (float *)RL_CALLOC(model.meshes[mi].vertexCount*2, sizeof(float)); + model.meshes[mi].normals = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float)); + model.meshMaterial[mi] = mi; + } + + // scan through the combined sub meshes and pick out each material mesh + for (unsigned int af = 0; af < attrib.num_faces; af++) + { + int mm = attrib.material_ids[af]; // mesh material for this face + if (mm == -1) { mm = 0; } // no material object.. + + // Get indices for the face + tinyobj_vertex_index_t idx0 = attrib.faces[3*af + 0]; + tinyobj_vertex_index_t idx1 = attrib.faces[3*af + 1]; + tinyobj_vertex_index_t idx2 = attrib.faces[3*af + 2]; + + // Fill vertices buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount[mm] +=3; + + if (attrib.num_texcoords > 0) + { + // Fill texcoords buffer (float) using vertex index of the face + // NOTE: Y-coordinate must be flipped upside-down to account for + // raylib's upside down textures... + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount[mm] += 2; + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount[mm] += 2; + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount[mm] += 2; + } + + if (attrib.num_normals > 0) + { + // Fill normals buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount[mm] +=3; + } + } + + // Init model materials + for (unsigned int m = 0; m < materialCount; m++) + { + // Init material to default + // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE + model.materials[m] = LoadMaterialDefault(); + + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // Get default texture, in case no texture is defined + + if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname); //char *diffuse_texname; // map_Kd + else model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); + + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].color = (Color){ (unsigned char)(materials[m].diffuse[0]*255.0f), (unsigned char)(materials[m].diffuse[1]*255.0f), (unsigned char)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f; + + if (materials[m].specular_texname != NULL) model.materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(materials[m].specular_texname); //char *specular_texname; // map_Ks + model.materials[m].maps[MATERIAL_MAP_SPECULAR].color = (Color){ (unsigned char)(materials[m].specular[0]*255.0f), (unsigned char)(materials[m].specular[1]*255.0f), (unsigned char)(materials[m].specular[2]*255.0f), 255 }; //float specular[3]; + model.materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f; + + if (materials[m].bump_texname != NULL) model.materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(materials[m].bump_texname); //char *bump_texname; // map_bump, bump + model.materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE; + model.materials[m].maps[MATERIAL_MAP_NORMAL].value = materials[m].shininess; + + model.materials[m].maps[MATERIAL_MAP_EMISSION].color = (Color){ (unsigned char)(materials[m].emission[0]*255.0f), (unsigned char)(materials[m].emission[1]*255.0f), (unsigned char)(materials[m].emission[2]*255.0f), 255 }; //float emission[3]; + + if (materials[m].displacement_texname != NULL) model.materials[m].maps[MATERIAL_MAP_HEIGHT].texture = LoadTexture(materials[m].displacement_texname); //char *displacement_texname; // disp + } + + tinyobj_attrib_free(&attrib); + tinyobj_shapes_free(meshes, meshCount); + tinyobj_materials_free(materials, materialCount); + + RL_FREE(fileData); + RL_FREE(matFaces); + + RL_FREE(vCount); + RL_FREE(vtCount); + RL_FREE(vnCount); + RL_FREE(faceCount); + + if (CHDIR(currentDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); + } + } + + return model; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_IQM) +// Load IQM mesh data +static Model LoadIQM(const char *fileName) +{ + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // only IQM version 2 supported + + #define BONE_NAME_LENGTH 32 // BoneInfo name string length + #define MESH_NAME_LENGTH 32 // Mesh name string length + #define MATERIAL_NAME_LENGTH 32 // Material name string length + + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + unsigned char *fileDataPtr = fileData; + + // IQM file structs + //----------------------------------------------------------------------------------- + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMMesh { + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; + } IQMMesh; + + typedef struct IQMTriangle { + unsigned int vertex[3]; + } IQMTriangle; + + typedef struct IQMJoint { + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; + } IQMJoint; + + typedef struct IQMVertexArray { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; + } IQMVertexArray; + + // NOTE: Below IQM structures are not used but listed for reference + /* + typedef struct IQMAdjacency { + unsigned int triangle[3]; + } IQMAdjacency; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + typedef struct IQMBounds { + float bbmin[3], bbmax[3]; + float xyradius, radius; + } IQMBounds; + */ + //----------------------------------------------------------------------------------- + + // IQM vertex data types + enum { + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, // NOTE: Tangents unused by default + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, // NOTE: Vertex colors unused by default + IQM_CUSTOM = 0x10 // NOTE: Custom vertex values unused by default + }; + + Model model = { 0 }; + + IQMMesh *imesh = NULL; + IQMTriangle *tri = NULL; + IQMVertexArray *va = NULL; + IQMJoint *ijoint = NULL; + + float *vertex = NULL; + float *normal = NULL; + float *text = NULL; + char *blendi = NULL; + unsigned char *blendw = NULL; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return model; + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + return model; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + return model; + } + + //fileDataPtr += sizeof(IQMHeader); // Move file data pointer + + // Meshes data processing + imesh = RL_MALLOC(sizeof(IQMMesh)*iqmHeader->num_meshes); + //fseek(iqmFile, iqmHeader->ofs_meshes, SEEK_SET); + //fread(imesh, sizeof(IQMMesh)*iqmHeader->num_meshes, 1, iqmFile); + memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh)); + + model.meshCount = iqmHeader->num_meshes; + model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + + model.materialCount = model.meshCount; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + + char name[MESH_NAME_LENGTH] = { 0 }; + char material[MATERIAL_NAME_LENGTH] = { 0 }; + + for (int i = 0; i < model.meshCount; i++) + { + //fseek(iqmFile, iqmHeader->ofs_text + imesh[i].name, SEEK_SET); + //fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile); + memcpy(name, fileDataPtr + iqmHeader->ofs_text + imesh[i].name, MESH_NAME_LENGTH*sizeof(char)); + + //fseek(iqmFile, iqmHeader->ofs_text + imesh[i].material, SEEK_SET); + //fread(material, sizeof(char)*MATERIAL_NAME_LENGTH, 1, iqmFile); + memcpy(material, fileDataPtr + iqmHeader->ofs_text + imesh[i].material, MATERIAL_NAME_LENGTH*sizeof(char)); + + model.materials[i] = LoadMaterialDefault(); + + TRACELOG(LOG_DEBUG, "MODEL: [%s] mesh name (%s), material (%s)", fileName, name, material); + + model.meshes[i].vertexCount = imesh[i].num_vertexes; + + model.meshes[i].vertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions + model.meshes[i].normals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex normals + model.meshes[i].texcoords = RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); // Default vertex texcoords + + model.meshes[i].boneIds = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + model.meshes[i].boneWeights = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + + model.meshes[i].triangleCount = imesh[i].num_triangles; + model.meshes[i].indices = RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short)); + + // Animated verted data, what we actually process for rendering + // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) + model.meshes[i].animVertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); + model.meshes[i].animNormals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); + } + + // Triangles data processing + tri = RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle)); + //fseek(iqmFile, iqmHeader->ofs_triangles, SEEK_SET); + //fread(tri, iqmHeader->num_triangles*sizeof(IQMTriangle), 1, iqmFile); + memcpy(tri, fileDataPtr + iqmHeader->ofs_triangles, iqmHeader->num_triangles*sizeof(IQMTriangle)); + + for (int m = 0; m < model.meshCount; m++) + { + int tcounter = 0; + + for (unsigned int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++) + { + // IQM triangles indexes are stored in counter-clockwise, but raylib processes the index in linear order, + // expecting they point to the counter-clockwise vertex triangle, so we need to reverse triangle indexes + // NOTE: raylib renders vertex data in counter-clockwise order (standard convention) by default + model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex; + tcounter += 3; + } + } + + // Vertex arrays data processing + va = RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); + //fseek(iqmFile, iqmHeader->ofs_vertexarrays, SEEK_SET); + //fread(va, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray), 1, iqmFile); + memcpy(va, fileDataPtr + iqmHeader->ofs_vertexarrays, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); + + for (unsigned int i = 0; i < iqmHeader->num_vertexarrays; i++) + { + switch (va[i].type) + { + case IQM_POSITION: + { + vertex = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(vertex, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); + memcpy(vertex, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].vertices[vCounter] = vertex[i]; + model.meshes[m].animVertices[vCounter] = vertex[i]; + vCounter++; + } + } + } break; + case IQM_NORMAL: + { + normal = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(normal, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); + memcpy(normal, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].normals[vCounter] = normal[i]; + model.meshes[m].animNormals[vCounter] = normal[i]; + vCounter++; + } + } + } break; + case IQM_TEXCOORD: + { + text = RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(text, iqmHeader->num_vertexes*2*sizeof(float), 1, iqmFile); + memcpy(text, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*2*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++) + { + model.meshes[m].texcoords[vCounter] = text[i]; + vCounter++; + } + } + } break; + case IQM_BLENDINDEXES: + { + blendi = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(blendi, iqmHeader->num_vertexes*4*sizeof(char), 1, iqmFile); + memcpy(blendi, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(char)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int boneCounter = 0; + for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneIds[boneCounter] = blendi[i]; + boneCounter++; + } + } + } break; + case IQM_BLENDWEIGHTS: + { + blendw = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); + //fseek(iqmFile, va[i].offset, SEEK_SET); + //fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile); + memcpy(blendw, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int boneCounter = 0; + for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneWeights[boneCounter] = blendw[i]/255.0f; + boneCounter++; + } + } + } break; + } + } + + // Bones (joints) data processing + ijoint = RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint)); + //fseek(iqmFile, iqmHeader->ofs_joints, SEEK_SET); + //fread(ijoint, iqmHeader->num_joints*sizeof(IQMJoint), 1, iqmFile); + memcpy(ijoint, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint)); + + model.boneCount = iqmHeader->num_joints; + model.bones = RL_MALLOC(iqmHeader->num_joints*sizeof(BoneInfo)); + model.bindPose = RL_MALLOC(iqmHeader->num_joints*sizeof(Transform)); + + for (unsigned int i = 0; i < iqmHeader->num_joints; i++) + { + // Bones + model.bones[i].parent = ijoint[i].parent; + //fseek(iqmFile, iqmHeader->ofs_text + ijoint[i].name, SEEK_SET); + //fread(model.bones[i].name, BONE_NAME_LENGTH*sizeof(char), 1, iqmFile); + memcpy(model.bones[i].name, fileDataPtr + iqmHeader->ofs_text + ijoint[i].name, BONE_NAME_LENGTH*sizeof(char)); + + // Bind pose (base pose) + model.bindPose[i].translation.x = ijoint[i].translate[0]; + model.bindPose[i].translation.y = ijoint[i].translate[1]; + model.bindPose[i].translation.z = ijoint[i].translate[2]; + + model.bindPose[i].rotation.x = ijoint[i].rotate[0]; + model.bindPose[i].rotation.y = ijoint[i].rotate[1]; + model.bindPose[i].rotation.z = ijoint[i].rotate[2]; + model.bindPose[i].rotation.w = ijoint[i].rotate[3]; + + model.bindPose[i].scale.x = ijoint[i].scale[0]; + model.bindPose[i].scale.y = ijoint[i].scale[1]; + model.bindPose[i].scale.z = ijoint[i].scale[2]; + } + + // Build bind pose from parent joints + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent >= 0) + { + model.bindPose[i].rotation = QuaternionMultiply(model.bindPose[model.bones[i].parent].rotation, model.bindPose[i].rotation); + model.bindPose[i].translation = Vector3RotateByQuaternion(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].rotation); + model.bindPose[i].translation = Vector3Add(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].translation); + model.bindPose[i].scale = Vector3Multiply(model.bindPose[i].scale, model.bindPose[model.bones[i].parent].scale); + } + } + + RL_FREE(fileData); + + RL_FREE(imesh); + RL_FREE(tri); + RL_FREE(va); + RL_FREE(vertex); + RL_FREE(normal); + RL_FREE(text); + RL_FREE(blendi); + RL_FREE(blendw); + RL_FREE(ijoint); + + return model; +} + +// Load IQM animation data +static ModelAnimation* LoadIQMModelAnimations(const char* fileName, int* animCount) +{ +#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number +#define IQM_VERSION 2 // only IQM version 2 supported + + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + unsigned char *fileDataPtr = fileData; + + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return NULL; + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + return NULL; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + return NULL; + } + + // Get bones data + IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); + //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); + //fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile); + memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); + + // Get animations data + *animCount = iqmHeader->num_anims; + IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); + //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); + //fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile); + memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); + + ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); + + // frameposes + unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); + //fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile); + memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + + for (unsigned int a = 0; a < iqmHeader->num_anims; a++) + { + animations[a].frameCount = anim[a].num_frames; + animations[a].boneCount = iqmHeader->num_poses; + animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); + animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); + // animations[a].framerate = anim.framerate; // TODO: Use framerate? + + for (unsigned int j = 0; j < iqmHeader->num_poses; j++) + { + strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); + animations[a].bones[j].parent = poses[j].parent; + } + + for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); + + int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; + + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (unsigned int i = 0; i < iqmHeader->num_poses; i++) + { + animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation); + } + } + + // Build frameposes + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (int i = 0; i < animations[a].boneCount; i++) + { + if (animations[a].bones[i].parent >= 0) + { + animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation); + animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation); + animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation); + animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale); + } + } + } + } + + RL_FREE(fileData); + + RL_FREE(framedata); + RL_FREE(poses); + RL_FREE(anim); + + return animations; +} + +#endif + +#if defined(SUPPORT_FILEFORMAT_GLTF) + +static const unsigned char base64Table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51 +}; + +static int GetSizeBase64(char *input) +{ + int size = 0; + + for (int i = 0; input[4*i] != 0; i++) + { + if (input[4*i + 3] == '=') + { + if (input[4*i + 2] == '=') size += 1; + else size += 2; + } + else size += 3; + } + + return size; +} + +static unsigned char *DecodeBase64(char *input, int *size) +{ + *size = GetSizeBase64(input); + + unsigned char *buf = (unsigned char *)RL_MALLOC(*size); + for (int i = 0; i < *size/3; i++) + { + unsigned char a = base64Table[(int)input[4*i]]; + unsigned char b = base64Table[(int)input[4*i + 1]]; + unsigned char c = base64Table[(int)input[4*i + 2]]; + unsigned char d = base64Table[(int)input[4*i + 3]]; + + buf[3*i] = (a << 2) | (b >> 4); + buf[3*i + 1] = (b << 4) | (c >> 2); + buf[3*i + 2] = (c << 6) | d; + } + + if (*size%3 == 1) + { + int n = *size/3; + unsigned char a = base64Table[(int)input[4*n]]; + unsigned char b = base64Table[(int)input[4*n + 1]]; + buf[*size - 1] = (a << 2) | (b >> 4); + } + else if (*size%3 == 2) + { + int n = *size/3; + unsigned char a = base64Table[(int)input[4*n]]; + unsigned char b = base64Table[(int)input[4*n + 1]]; + unsigned char c = base64Table[(int)input[4*n + 2]]; + buf[*size - 2] = (a << 2) | (b >> 4); + buf[*size - 1] = (b << 4) | (c >> 2); + } + return buf; +} + +// Load texture from cgltf_image +static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Color tint) +{ + Image rimage = { 0 }; + + if (image->uri) + { + if ((strlen(image->uri) > 5) && + (image->uri[0] == 'd') && + (image->uri[1] == 'a') && + (image->uri[2] == 't') && + (image->uri[3] == 'a') && + (image->uri[4] == ':')) + { + // Data URI + // Format: data:;base64, + + // Find the comma + int i = 0; + while ((image->uri[i] != ',') && (image->uri[i] != 0)) i++; + + if (image->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image"); + else + { + int size = 0; + unsigned char *data = DecodeBase64(image->uri + i + 1, &size); + + int width, height; + unsigned char *raw = stbi_load_from_memory(data, size, &width, &height, NULL, 4); + RL_FREE(data); + + rimage.data = raw; + rimage.width = width; + rimage.height = height; + rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + rimage.mipmaps = 1; + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + } + } + else + { + rimage = LoadImage(TextFormat("%s/%s", texPath, image->uri)); + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + } + } + else if (image->buffer_view) + { + unsigned char *data = RL_MALLOC(image->buffer_view->size); + int n = (int)image->buffer_view->offset; + int stride = (int)image->buffer_view->stride ? (int)image->buffer_view->stride : 1; + + for (unsigned int i = 0; i < image->buffer_view->size; i++) + { + data[i] = ((unsigned char *)image->buffer_view->buffer->data)[n]; + n += stride; + } + + int width, height; + unsigned char *raw = stbi_load_from_memory(data, (int)image->buffer_view->size, &width, &height, NULL, 4); + RL_FREE(data); + + rimage.data = raw; + rimage.width = width; + rimage.height = height; + rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + rimage.mipmaps = 1; + + // TODO: Tint shouldn't be applied here! + ImageColorTint(&rimage, tint); + } + else rimage = GenImageColor(1, 1, tint); + + return rimage; +} + + +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable, unsigned int elements, unsigned int size) +{ + if (acc->count == 2) + { + if (index > 1) return false; + + memcpy(variable, index == 0 ? acc->min : acc->max, elements*size); + return true; + } + + unsigned int stride = size*elements; + memset(variable, 0, stride); + + if (acc->buffer_view == NULL || acc->buffer_view->buffer == NULL || acc->buffer_view->buffer->data == NULL) return false; + + void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index*stride) + acc->buffer_view->offset + acc->offset; + memcpy(variable, readPosition, stride); + return true; +} + +// LoadGLTF loads in model data from given filename, supporting both .gltf and .glb +static Model LoadGLTF(const char *fileName) +{ + /*********************************************************************************** + + Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) and Hristo Stamenov(@object71) + + Features: + - Supports .gltf and .glb files + - Supports embedded (base64) or external textures + - Loads all raylib supported material textures, values and colors + - Supports multiple mesh per model and multiple primitives per model + + Some restrictions (not exhaustive): + - Triangle-only meshes + - Not supported node hierarchies or transforms + - Only supports unsigned short indices (no byte/unsigned int) + - Only supports float for texture coordinates (no byte/unsigned short) + + *************************************************************************************/ + + Model model = { 0 }; + + // glTF file loading + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData == NULL) return model; + + // glTF data loading + cgltf_options options = { 0 }; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + + if (result == cgltf_result_success) + { + TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count); + TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->materials_count); + + // Read data buffers + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); + + int primitivesCount = 0; + + for (unsigned int i = 0; i < data->meshes_count; i++) + primitivesCount += (int)data->meshes[i].primitives_count; + + // Process glTF data and map to model + model.meshCount = primitivesCount; + model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.materialCount = (int)data->materials_count + 1; + model.materials = RL_MALLOC(model.materialCount*sizeof(Material)); + model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int)); + model.boneCount = (int)data->nodes_count; + model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo)); + model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform)); + + InitGLTFBones(&model, data); + LoadGLTFMaterial(&model, fileName, data); + + int primitiveIndex = 0; + + for (unsigned int i = 0; i < data->meshes_count; i++) + { + for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++) + { + for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++) + { + if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + model.meshes[primitiveIndex].vertexCount = (int)acc->count; + int bufferSize = model.meshes[primitiveIndex].vertexCount*3*sizeof(float); + model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize); + model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].vertices + (a*3), 3, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + int readValue[3]; + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].vertices[(a*3) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].vertices[(a*3) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].vertices[(a*3) + 2] = (float)readValue[2]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short vertices + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF vertices must be float or int", fileName); + } + + memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + int bufferSize = (int)(acc->count*3*sizeof(float)); + model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize); + model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].normals + (a*3), 3, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + int readValue[3]; + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].normals[(a*3) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].normals[(a*3) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].normals[(a*3) + 2] = (float)readValue[2]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short normals + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); + } + + memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + if (acc->component_type == cgltf_component_type_r_32f) + { + model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float)); + + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a*2), 2, sizeof(float)); + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short texture coordinates + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); + } + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + LoadGLTFBoneAttribute(&model, acc, data, primitiveIndex); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float)); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].boneWeights + (a*4), 4, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + unsigned int readValue[4]; + for (int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int)); + model.meshes[primitiveIndex].normals[(a*4) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].normals[(a*4) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].normals[(a*4) + 2] = (float)readValue[2]; + model.meshes[primitiveIndex].normals[(a*4) + 3] = (float)readValue[3]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short weights + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); + } + } + } + + cgltf_accessor *acc = data->meshes[i].primitives[p].indices; + LoadGLTFModelIndices(&model, acc, primitiveIndex); + + if (data->meshes[i].primitives[p].material) + { + // Compute the offset + model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials); + } + else + { + model.meshMaterial[primitiveIndex] = model.materialCount - 1; + } + + BindGLTFPrimitiveToBones(&model, data, primitiveIndex); + + primitiveIndex++; + } + + } + + cgltf_free(data); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); + + RL_FREE(fileData); + + return model; +} + +static void InitGLTFBones(Model* model, const cgltf_data* data) +{ + for (unsigned int j = 0; j < data->nodes_count; j++) + { + strcpy(model->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); + model->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1; + } + + for (unsigned int i = 0; i < data->nodes_count; i++) + { + if (data->nodes[i].has_translation) memcpy(&model->bindPose[i].translation, data->nodes[i].translation, 3*sizeof(float)); + else model->bindPose[i].translation = Vector3Zero(); + + if (data->nodes[i].has_rotation) memcpy(&model->bindPose[i].rotation, data->nodes[i].rotation, 4*sizeof(float)); + else model->bindPose[i].rotation = QuaternionIdentity(); + + model->bindPose[i].rotation = QuaternionNormalize(model->bindPose[i].rotation); + + if (data->nodes[i].has_scale) memcpy(&model->bindPose[i].scale, data->nodes[i].scale, 3*sizeof(float)); + else model->bindPose[i].scale = Vector3One(); + } + + { + bool* completedBones = RL_CALLOC(model->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < model->boneCount) { + for (int i = 0; i < model->boneCount; i++) + { + if (completedBones[i]) continue; + + if (model->bones[i].parent < 0) { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[model->bones[i].parent]) continue; + + Transform* currentTransform = &model->bindPose[i]; + BoneInfo* currentBone = &model->bones[i]; + int root = currentBone->parent; + if (root >= model->boneCount) + root = 0; + Transform* parentTransform = &model->bindPose[root]; + + currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); + currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); + currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation); + currentTransform->scale = Vector3Multiply(currentTransform->scale, parentTransform->scale); + completedBones[i] = true; + numberCompletedBones++; + } + } + + RL_FREE(completedBones); + } +} + +static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data) +{ + for (int i = 0; i < model->materialCount - 1; i++) + { + model->materials[i] = LoadMaterialDefault(); + Color tint = (Color){ 255, 255, 255, 255 }; + const char *texPath = GetDirectoryPath(fileName); + + // Ensure material follows raylib support for PBR (metallic/roughness flow) + if (data->materials[i].has_pbr_metallic_roughness) + { + tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); + tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); + tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); + tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); + + model->materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint; + + if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) + { + Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo); + UnloadImage(albedo); + } + + tint = WHITE; // Set tint to white after it's been used by Albedo + + if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) + { + Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); + + float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; + + float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; + model->materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic; + + UnloadImage(metallicRoughness); + } + + if (data->materials[i].normal_texture.texture) + { + Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage); + UnloadImage(normalImage); + } + + if (data->materials[i].occlusion_texture.texture) + { + Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); + UnloadImage(occulsionImage); + } + + if (data->materials[i].emissive_texture.texture) + { + Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); + tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); + tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); + tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); + model->materials[i].maps[MATERIAL_MAP_EMISSION].color = tint; + UnloadImage(emissiveImage); + } + } + } + + model->materials[model->materialCount - 1] = LoadMaterialDefault(); +} + +static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex) +{ + if (jointsAccessor->component_type == cgltf_component_type_r_16u) + { + model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int)*jointsAccessor->count*4); + short* bones = RL_MALLOC(sizeof(short)*jointsAccessor->count*4); + + for (int a = 0; a < jointsAccessor->count; a++) + { + GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(short)); + } + + for (unsigned int a = 0; a < jointsAccessor->count*4; a++) + { + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (&(data->nodes[k]) == skinJoint) + { + model->meshes[primitiveIndex].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else if (jointsAccessor->component_type == cgltf_component_type_r_8u) + { + model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int)*jointsAccessor->count*4); + unsigned char* bones = RL_MALLOC(sizeof(unsigned char)*jointsAccessor->count*4); + + for (int a = 0; a < jointsAccessor->count; a++) + { + GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(unsigned char)); + } + + for (unsigned int a = 0; a < jointsAccessor->count*4; a++) + { + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (&(data->nodes[k]) == skinJoint) + { + model->meshes[primitiveIndex].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else + { + // TODO: Support other size of bone index? + TRACELOG(LOG_WARNING, "MODEL: glTF bones in unexpected format"); + } +} + +static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex) +{ + if (model->meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0) + { + for (int nodeId = 0; nodeId < data->nodes_count; nodeId++) + { + if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) + { + model->meshes[primitiveIndex].boneIds = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(int)); + model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(float)); + + for (int b = 0; b < 4*model->meshes[primitiveIndex].vertexCount; b++) + { + if (b%4 == 0) + { + model->meshes[primitiveIndex].boneIds[b] = nodeId; + model->meshes[primitiveIndex].boneWeights[b] = 1.0f; + } + else + { + model->meshes[primitiveIndex].boneIds[b] = 0; + model->meshes[primitiveIndex].boneWeights[b] = 0.0f; + } + + } + + Vector3 boundVertex = { 0 }; + Vector3 boundNormal = { 0 }; + + Vector3 outTranslation = { 0 }; + Quaternion outRotation = { 0 }; + Vector3 outScale = { 0 }; + + int vCounter = 0; + int boneCounter = 0; + int boneId = 0; + + for (int i = 0; i < model->meshes[primitiveIndex].vertexCount; i++) + { + boneId = model->meshes[primitiveIndex].boneIds[boneCounter]; + outTranslation = model->bindPose[boneId].translation; + outRotation = model->bindPose[boneId].rotation; + outScale = model->bindPose[boneId].scale; + + // Vertices processing + boundVertex = (Vector3){ model->meshes[primitiveIndex].vertices[vCounter], model->meshes[primitiveIndex].vertices[vCounter + 1], model->meshes[primitiveIndex].vertices[vCounter + 2] }; + boundVertex = Vector3Multiply(boundVertex, outScale); + boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation); + boundVertex = Vector3Add(boundVertex, outTranslation); + model->meshes[primitiveIndex].vertices[vCounter] = boundVertex.x; + model->meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y; + model->meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z; + + // Normals processing + if (model->meshes[primitiveIndex].normals != NULL) + { + boundNormal = (Vector3){ model->meshes[primitiveIndex].normals[vCounter], model->meshes[primitiveIndex].normals[vCounter + 1], model->meshes[primitiveIndex].normals[vCounter + 2] }; + boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation); + model->meshes[primitiveIndex].normals[vCounter] = boundNormal.x; + model->meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y; + model->meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z; + } + + vCounter += 3; + boneCounter += 4; + } + } + } + } +} + +static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex) +{ + if (indexAccessor) + { + if (indexAccessor->component_type == cgltf_component_type_r_16u || indexAccessor->component_type == cgltf_component_type_r_16) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned short readValue = 0; + for (int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(short)); + model->meshes[primitiveIndex].indices[a] = readValue; + } + } + else if (indexAccessor->component_type == cgltf_component_type_r_8u || indexAccessor->component_type == cgltf_component_type_r_8) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned char readValue = 0; + for (int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(char)); + model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; + } + } + else if (indexAccessor->component_type == cgltf_component_type_r_32u) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned int readValue; + for (int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(unsigned int)); + model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; + } + } + } + else + { + // Unindexed mesh + model->meshes[primitiveIndex].triangleCount = model->meshes[primitiveIndex].vertexCount/3; + } +} + +// LoadGLTF loads in animation data from given filename +static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount) +{ + /*********************************************************************************** + + Function implemented by Hristo Stamenov (@object71) + + Features: + - Supports .gltf and .glb files + + Some restrictions (not exhaustive): + - ... + + *************************************************************************************/ + + // glTF file loading + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + ModelAnimation *animations = NULL; + + if (fileData == NULL) return animations; + + // glTF data loading + cgltf_options options = { 0 }; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + + if (result == cgltf_result_success) + { + TRACELOG(LOG_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" : + "gltf", data->animations_count); + + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_WARNING, "MODEL: [%s] unable to load glTF animations data", fileName); + animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation)); + *animCount = (int)data->animations_count; + + for (unsigned int a = 0; a < data->animations_count; a++) + { + // gltf animation consists of the following structures: + // - nodes - bones + // - channels - single transformation type on a single bone + // - node - bone + // - transformation type (path) - translation, rotation, scale + // - sampler - animation samples + // - input - points in time this transformation happens + // - output - the transformation amount at the given input points in time + // - interpolation - the type of interpolation to use between the frames + + cgltf_animation *animation = data->animations + a; + + ModelAnimation *output = animations + a; + + // 30 frames sampled per second + const float timeStep = (1.0f/30.0f); + float animationDuration = 0.0f; + + // Getting the max animation time to consider for animation duration + for (unsigned int i = 0; i < animation->channels_count; i++) + { + cgltf_animation_channel* channel = animation->channels + i; + int frameCounts = (int)channel->sampler->input->count; + float lastFrameTime = 0.0f; + + if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float))) + { + animationDuration = fmaxf(lastFrameTime, animationDuration); + } + } + + output->frameCount = (int)(animationDuration / timeStep); + output->boneCount = (int)data->nodes_count; + output->bones = RL_MALLOC(output->boneCount*sizeof(BoneInfo)); + output->framePoses = RL_MALLOC(output->frameCount*sizeof(Transform *)); + // output->framerate = // TODO: Use framerate instead of const timestep + + // Name and parent bones + for (unsigned int j = 0; j < data->nodes_count; j++) + { + strcpy(output->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); + output->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1; + } + + // Allocate data for frames + // Initiate with zero bone translations + for (int frame = 0; frame < output->frameCount; frame++) + { + output->framePoses[frame] = RL_MALLOC(output->frameCount*data->nodes_count*sizeof(Transform)); + + for (unsigned int i = 0; i < data->nodes_count; i++) + { + output->framePoses[frame][i].translation = Vector3Zero(); + output->framePoses[frame][i].rotation = QuaternionIdentity(); + output->framePoses[frame][i].rotation = QuaternionNormalize(output->framePoses[frame][i].rotation); + output->framePoses[frame][i].scale = Vector3One(); + } + } + + // for each single transformation type on single bone + for (unsigned int channelId = 0; channelId < animation->channels_count; channelId++) + { + cgltf_animation_channel* channel = animation->channels + channelId; + cgltf_animation_sampler* sampler = channel->sampler; + + int boneId = (int)(channel->target_node - data->nodes); + + for (int frame = 0; frame < output->frameCount; frame++) + { + bool shouldSkipFurtherTransformation = true; + int outputMin = 0; + int outputMax = 0; + float frameTime = frame*timeStep; + float lerpPercent = 0.0f; + + // For this transformation: + // getting between which input values the current frame time position + // and also what is the percent to use in the linear interpolation later + for (unsigned int j = 0; j < sampler->input->count; j++) + { + float inputFrameTime; + if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float))) + { + if (frameTime < inputFrameTime) + { + shouldSkipFurtherTransformation = false; + outputMin = (j == 0) ? 0 : j - 1; + outputMax = j; + + float previousInputTime = 0.0f; + if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float))) + { + if ((inputFrameTime - previousInputTime) != 0) + { + lerpPercent = (frameTime - previousInputTime)/(inputFrameTime - previousInputTime); + } + } + + break; + } + } + else break; + } + + // If the current transformation has no information for the current frame time point + if (shouldSkipFurtherTransformation) continue; + + if (channel->target_path == cgltf_animation_path_type_translation) + { + Vector3 translationStart; + Vector3 translationEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success; + + if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent); + } + if (channel->target_path == cgltf_animation_path_type_rotation) + { + Quaternion rotationStart; + Quaternion rotationEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success; + + if (success) + { + output->framePoses[frame][boneId].rotation = QuaternionLerp(rotationStart, rotationEnd, lerpPercent); + output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation); + } + } + if (channel->target_path == cgltf_animation_path_type_scale) + { + Vector3 scaleStart; + Vector3 scaleEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success; + + if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent); + } + } + } + + // Build frameposes + for (int frame = 0; frame < output->frameCount; frame++) + { + bool *completedBones = RL_CALLOC(output->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < output->boneCount) + { + for (int i = 0; i < output->boneCount; i++) + { + if (completedBones[i]) continue; + + if (output->bones[i].parent < 0) + { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[output->bones[i].parent]) continue; + + output->framePoses[frame][i].rotation = QuaternionMultiply(output->framePoses[frame][output->bones[i].parent].rotation, output->framePoses[frame][i].rotation); + output->framePoses[frame][i].translation = Vector3RotateByQuaternion(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].rotation); + output->framePoses[frame][i].translation = Vector3Add(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].translation); + output->framePoses[frame][i].scale = Vector3Multiply(output->framePoses[frame][i].scale, output->framePoses[frame][output->bones[i].parent].scale); + completedBones[i] = true; + numberCompletedBones++; + } + } + + RL_FREE(completedBones); + } + + } + + cgltf_free(data); + } + else TRACELOG(LOG_WARNING, ": [%s] Failed to load glTF data", fileName); + + RL_FREE(fileData); + + return animations; +} + +#endif diff --git a/raylib_pi4_test/physac.h b/raylib_pi4_test/physac.h new file mode 100644 index 0000000..676a969 --- /dev/null +++ b/raylib_pi4_test/physac.h @@ -0,0 +1,1988 @@ +/********************************************************************************************** +* +* Physac v1.1 - 2D Physics library for videogames +* +* DESCRIPTION: +* +* Physac is a small 2D physics engine written in pure C. The engine uses a fixed time-step thread loop +* to simluate physics. A physics step contains the following phases: get collision information, +* apply dynamics, collision solving and position correction. It uses a very simple struct for physic +* bodies with a position vector to be used in any 3D rendering API. +* +* CONFIGURATION: +* +* #define PHYSAC_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define PHYSAC_STATIC (defined by default) +* The generated implementation will stay private inside implementation file and all +* internal symbols and functions will only be visible inside that file. +* +* #define PHYSAC_DEBUG +* Show debug traces log messages about physic bodies creation/destruction, physic system errors, +* some calculations results and NULL reference exceptions +* +* #define PHYSAC_DEFINE_VECTOR2_TYPE +* Forces library to define struct Vector2 data type (float x; float y) +* +* #define PHYSAC_AVOID_TIMMING_SYSTEM +* Disables internal timming system, used by UpdatePhysics() to launch timmed physic steps, +* it allows just running UpdatePhysics() automatically on a separate thread at a desired time step. +* In case physics steps update needs to be controlled by user with a custom timming mechanism, +* just define this flag and the internal timming mechanism will be avoided, in that case, +* timming libraries are neither required by the module. +* +* #define PHYSAC_MALLOC() +* #define PHYSAC_CALLOC() +* #define PHYSAC_FREE() +* You can define your own malloc/free implementation replacing stdlib.h malloc()/free() functions. +* Otherwise it will include stdlib.h and use the C standard library malloc()/free() function. +* +* COMPILATION: +* +* Use the following code to compile with GCC: +* gcc -o $(NAME_PART).exe $(FILE_NAME) -s -static -lraylib -lopengl32 -lgdi32 -lwinmm -std=c99 +* +* VERSIONS HISTORY: +* 1.1 (20-Jan-2021) @raysan5: Library general revision +* Removed threading system (up to the user) +* Support MSVC C++ compilation using CLITERAL() +* Review DEBUG mechanism for TRACELOG() and all TRACELOG() messages +* Review internal variables/functions naming for consistency +* Allow option to avoid internal timming system, to allow app manage the steps +* 1.0 (12-Jun-2017) First release of the library +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2016-2021 Victor Fisac (@victorfisac) and Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if !defined(PHYSAC_H) +#define PHYSAC_H + +#if defined(PHYSAC_STATIC) + #define PHYSACDEF static // Functions just visible to module including this file +#else + #if defined(__cplusplus) + #define PHYSACDEF extern "C" // Functions visible from other files (no name mangling of functions in C++) + #else + #define PHYSACDEF extern // Functions visible from other files + #endif +#endif + +// Allow custom memory allocators +#ifndef PHYSAC_MALLOC + #define PHYSAC_MALLOC(size) malloc(size) +#endif +#ifndef PHYSAC_CALLOC + #define PHYSAC_CALLOC(size, n) calloc(size, n) +#endif +#ifndef PHYSAC_FREE + #define PHYSAC_FREE(ptr) free(ptr) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define PHYSAC_MAX_BODIES 64 // Maximum number of physic bodies supported +#define PHYSAC_MAX_MANIFOLDS 4096 // Maximum number of physic bodies interactions (64x64) +#define PHYSAC_MAX_VERTICES 24 // Maximum number of vertex for polygons shapes +#define PHYSAC_DEFAULT_CIRCLE_VERTICES 24 // Default number of vertices for circle shapes + +#define PHYSAC_COLLISION_ITERATIONS 100 +#define PHYSAC_PENETRATION_ALLOWANCE 0.05f +#define PHYSAC_PENETRATION_CORRECTION 0.4f + +#define PHYSAC_PI 3.14159265358979323846f +#define PHYSAC_DEG2RAD (PHYSAC_PI/180.0f) + +//---------------------------------------------------------------------------------- +// Data Types Structure Definition +//---------------------------------------------------------------------------------- +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#endif + +typedef enum PhysicsShapeType { PHYSICS_CIRCLE = 0, PHYSICS_POLYGON } PhysicsShapeType; + +// Previously defined to be used in PhysicsShape struct as circular dependencies +typedef struct PhysicsBodyData *PhysicsBody; + +#if defined(PHYSAC_DEFINE_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#endif + +// Matrix2x2 type (used for polygon shape rotation matrix) +typedef struct Matrix2x2 { + float m00; + float m01; + float m10; + float m11; +} Matrix2x2; + +typedef struct PhysicsVertexData { + unsigned int vertexCount; // Vertex count (positions and normals) + Vector2 positions[PHYSAC_MAX_VERTICES]; // Vertex positions vectors + Vector2 normals[PHYSAC_MAX_VERTICES]; // Vertex normals vectors +} PhysicsVertexData; + +typedef struct PhysicsShape { + PhysicsShapeType type; // Shape type (circle or polygon) + PhysicsBody body; // Shape physics body data pointer + PhysicsVertexData vertexData; // Shape vertices data (used for polygon shapes) + float radius; // Shape radius (used for circle shapes) + Matrix2x2 transform; // Vertices transform matrix 2x2 +} PhysicsShape; + +typedef struct PhysicsBodyData { + unsigned int id; // Unique identifier + bool enabled; // Enabled dynamics state (collisions are calculated anyway) + Vector2 position; // Physics body shape pivot + Vector2 velocity; // Current linear velocity applied to position + Vector2 force; // Current linear force (reset to 0 every step) + float angularVelocity; // Current angular velocity applied to orient + float torque; // Current angular force (reset to 0 every step) + float orient; // Rotation in radians + float inertia; // Moment of inertia + float inverseInertia; // Inverse value of inertia + float mass; // Physics body mass + float inverseMass; // Inverse value of mass + float staticFriction; // Friction when the body has not movement (0 to 1) + float dynamicFriction; // Friction when the body has movement (0 to 1) + float restitution; // Restitution coefficient of the body (0 to 1) + bool useGravity; // Apply gravity force to dynamics + bool isGrounded; // Physics grounded on other body state + bool freezeOrient; // Physics rotation constraint + PhysicsShape shape; // Physics body shape information (type, radius, vertices, transform) +} PhysicsBodyData; + +typedef struct PhysicsManifoldData { + unsigned int id; // Unique identifier + PhysicsBody bodyA; // Manifold first physics body reference + PhysicsBody bodyB; // Manifold second physics body reference + float penetration; // Depth of penetration from collision + Vector2 normal; // Normal direction vector from 'a' to 'b' + Vector2 contacts[2]; // Points of contact during collision + unsigned int contactsCount; // Current collision number of contacts + float restitution; // Mixed restitution during collision + float dynamicFriction; // Mixed dynamic friction during collision + float staticFriction; // Mixed static friction during collision +} PhysicsManifoldData, *PhysicsManifold; + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// Physics system management +PHYSACDEF void InitPhysics(void); // Initializes physics system +PHYSACDEF void UpdatePhysics(void); // Update physics system +PHYSACDEF void ResetPhysics(void); // Reset physics system (global variables) +PHYSACDEF void ClosePhysics(void); // Close physics system and unload used memory +PHYSACDEF void SetPhysicsTimeStep(double delta); // Sets physics fixed time step in milliseconds. 1.666666 by default +PHYSACDEF void SetPhysicsGravity(float x, float y); // Sets physics global gravity force + +// Physic body creation/destroy +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density); // Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density); // Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density); // Creates a new polygon physics body with generic parameters +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body); // Destroy a physics body + +// Physic body forces +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force); // Adds a force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount); // Adds an angular force to a physics body +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force); // Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians); // Sets physics body shape transform based on radians parameter + +// Query physics info +PHYSACDEF PhysicsBody GetPhysicsBody(int index); // Returns a physics body of the bodies pool at a specific index +PHYSACDEF int GetPhysicsBodiesCount(void); // Returns the current amount of created physics bodies +PHYSACDEF int GetPhysicsShapeType(int index); // Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeVerticesCount(int index); // Returns the amount of vertices of a physics body shape +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex); // Returns transformed position of a body shape (body position + vertex transformed position) + +#if defined(__cplusplus) +} +#endif + +#endif // PHYSAC_H + +/*********************************************************************************** +* +* PHYSAC IMPLEMENTATION +* +************************************************************************************/ + +#if defined(PHYSAC_IMPLEMENTATION) + +// Support TRACELOG macros +#if defined(PHYSAC_DEBUG) + #include // Required for: printf() + #define TRACELOG(...) printf(__VA_ARGS__) +#else + #define TRACELOG(...) (void)0; +#endif + +#include // Required for: malloc(), calloc(), free() +#include // Required for: cosf(), sinf(), fabs(), sqrtf() + +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) + // Time management functionality + #include // Required for: time(), clock_gettime() + #if defined(_WIN32) + // Functions required to query time on Windows + int __stdcall QueryPerformanceCounter(unsigned long long int *lpPerformanceCount); + int __stdcall QueryPerformanceFrequency(unsigned long long int *lpFrequency); + #endif + #if defined(__linux__) || defined(__FreeBSD__) + #if _POSIX_C_SOURCE < 199309L + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. + #endif + #include // Required for: timespec + #endif + #if defined(__APPLE__) // macOS also defines __MACH__ + #include // Required for: mach_absolute_time() + #endif +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized from { } initializers. +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define PHYSAC_MIN(a,b) (((a)<(b))?(a):(b)) +#define PHYSAC_MAX(a,b) (((a)>(b))?(a):(b)) +#define PHYSAC_FLT_MAX 3.402823466e+38f +#define PHYSAC_EPSILON 0.000001f +#define PHYSAC_K 1.0f/3.0f +#define PHYSAC_VECTOR_ZERO CLITERAL(Vector2){ 0.0f, 0.0f } + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static double deltaTime = 1.0/60.0/10.0 * 1000; // Delta time in milliseconds used for physics steps + +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) +// Time measure variables +static double baseClockTicks = 0.0; // Offset clock ticks for MONOTONIC clock +static unsigned long long int frequency = 0; // Hi-res clock frequency +static double startTime = 0.0; // Start time in milliseconds +static double currentTime = 0.0; // Current time in milliseconds +#endif + +// Physics system configuration +static PhysicsBody bodies[PHYSAC_MAX_BODIES]; // Physics bodies pointers array +static unsigned int physicsBodiesCount = 0; // Physics world current bodies counter +static PhysicsManifold contacts[PHYSAC_MAX_MANIFOLDS]; // Physics bodies pointers array +static unsigned int physicsManifoldsCount = 0; // Physics world current manifolds counter + +static Vector2 gravityForce = { 0.0f, 9.81f }; // Physics world gravity force + +// Utilities variables +static unsigned int usedMemory = 0; // Total allocated dynamic memory + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) +// Timming measure functions +static void InitTimer(void); // Initializes hi-resolution MONOTONIC timer +static unsigned long long int GetClockTicks(void); // Get hi-res MONOTONIC time measure in mseconds +static double GetCurrentTime(void); // Get current time measure in milliseconds +#endif + +static void UpdatePhysicsStep(void); // Update physics step (dynamics, collisions and position corrections) + +static int FindAvailableBodyIndex(); // Finds a valid index for a new physics body initialization +static int FindAvailableManifoldIndex(); // Finds a valid index for a new manifold initialization +static PhysicsVertexData CreateDefaultPolygon(float radius, int sides); // Creates a random polygon shape with max vertex distance from polygon pivot +static PhysicsVertexData CreateRectanglePolygon(Vector2 pos, Vector2 size); // Creates a rectangle polygon shape based on a min and max positions + +static void InitializePhysicsManifolds(PhysicsManifold manifold); // Initializes physics manifolds to solve collisions +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b); // Creates a new physics manifold to solve collision +static void DestroyPhysicsManifold(PhysicsManifold manifold); // Unitializes and destroys a physics manifold + +static void SolvePhysicsManifold(PhysicsManifold manifold); // Solves a created physics manifold between two physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold); // Solves collision between two circle shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold); // Solves collision between a circle to a polygon shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold); // Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold); // Solves collision between two polygons shape physics bodies +static void IntegratePhysicsForces(PhysicsBody body); // Integrates physics forces into velocity +static void IntegratePhysicsVelocity(PhysicsBody body); // Integrates physics velocity into position and forces +static void IntegratePhysicsImpulses(PhysicsManifold manifold); // Integrates physics collisions impulses to solve collisions +static void CorrectPhysicsPositions(PhysicsManifold manifold); // Corrects physics bodies positions based on manifolds collision information +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index); // Finds two polygon shapes incident face +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB); // Finds polygon shapes axis least penetration + +// Math required functions +static Vector2 MathVector2Product(Vector2 vector, float value); // Returns the product of a vector and a value +static float MathVector2CrossProduct(Vector2 v1, Vector2 v2); // Returns the cross product of two vectors +static float MathVector2SqrLen(Vector2 vector); // Returns the len square root of a vector +static float MathVector2DotProduct(Vector2 v1, Vector2 v2); // Returns the dot product of two vectors +static inline float MathVector2SqrDistance(Vector2 v1, Vector2 v2); // Returns the square root of distance between two vectors +static void MathVector2Normalize(Vector2 *vector); // Returns the normalized values of a vector +static Vector2 MathVector2Add(Vector2 v1, Vector2 v2); // Returns the sum of two given vectors +static Vector2 MathVector2Subtract(Vector2 v1, Vector2 v2); // Returns the subtract of two given vectors +static Matrix2x2 MathMatFromRadians(float radians); // Returns a matrix 2x2 from a given radians value +static inline Matrix2x2 MathMatTranspose(Matrix2x2 matrix); // Returns the transpose of a given matrix 2x2 +static inline Vector2 MathMatVector2Product(Matrix2x2 matrix, Vector2 vector); // Returns product between matrix 2x2 and vector +static int MathVector2Clip(Vector2 normal, Vector2 *faceA, Vector2 *faceB, float clip); // Returns clipping value based on a normal and two faces +static Vector2 MathTriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3); // Returns the barycenter of a triangle given by 3 points + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Initializes physics values, pointers and creates physics loop thread +PHYSACDEF void InitPhysics(void) +{ +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) + // Initialize high resolution timer + InitTimer(); +#endif + + TRACELOG("[PHYSAC] Physics module initialized successfully\n"); +} + +// Sets physics global gravity force +PHYSACDEF void SetPhysicsGravity(float x, float y) +{ + gravityForce.x = x; + gravityForce.y = y; +} + +// Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density) +{ + PhysicsBody body = CreatePhysicsBodyPolygon(pos, radius, PHYSAC_DEFAULT_CIRCLE_VERTICES, density); + return body; +} + +// Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density) +{ + // NOTE: Make sure body data is initialized to 0 + PhysicsBody body = (PhysicsBody)PHYSAC_CALLOC(sizeof(PhysicsBodyData), 1); + usedMemory += sizeof(PhysicsBodyData); + + int id = FindAvailableBodyIndex(); + if (id != -1) + { + // Initialize new body with generic values + body->id = id; + body->enabled = true; + body->position = pos; + body->shape.type = PHYSICS_POLYGON; + body->shape.body = body; + body->shape.transform = MathMatFromRadians(0.0f); + body->shape.vertexData = CreateRectanglePolygon(pos, CLITERAL(Vector2){ width, height }); + + // Calculate centroid and moment of inertia + Vector2 center = { 0.0f, 0.0f }; + float area = 0.0f; + float inertia = 0.0f; + + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = body->shape.vertexData.positions[i]; + unsigned int nextIndex = (((i + 1) < body->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 p2 = body->shape.vertexData.positions[nextIndex]; + + float D = MathVector2CrossProduct(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*PHYSAC_K*(p1.x + p2.x); + center.y += triangleArea*PHYSAC_K*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*PHYSAC_K*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + body->shape.vertexData.positions[i].x -= center.x; + body->shape.vertexData.positions[i].y -= center.y; + } + + body->mass = density*area; + body->inverseMass = ((body->mass != 0.0f) ? 1.0f/body->mass : 0.0f); + body->inertia = density*inertia; + body->inverseInertia = ((body->inertia != 0.0f) ? 1.0f/body->inertia : 0.0f); + body->staticFriction = 0.4f; + body->dynamicFriction = 0.2f; + body->restitution = 0.0f; + body->useGravity = true; + body->isGrounded = false; + body->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = body; + physicsBodiesCount++; + + TRACELOG("[PHYSAC] Physic body created successfully (id: %i)\n", body->id); + } + else TRACELOG("[PHYSAC] Physic body could not be created, PHYSAC_MAX_BODIES reached\n"); + + return body; +} + +// Creates a new polygon physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density) +{ + PhysicsBody body = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int id = FindAvailableBodyIndex(); + if (id != -1) + { + // Initialize new body with generic values + body->id = id; + body->enabled = true; + body->position = pos; + body->velocity = PHYSAC_VECTOR_ZERO; + body->force = PHYSAC_VECTOR_ZERO; + body->angularVelocity = 0.0f; + body->torque = 0.0f; + body->orient = 0.0f; + body->shape.type = PHYSICS_POLYGON; + body->shape.body = body; + body->shape.transform = MathMatFromRadians(0.0f); + body->shape.vertexData = CreateDefaultPolygon(radius, sides); + + // Calculate centroid and moment of inertia + Vector2 center = { 0.0f, 0.0f }; + float area = 0.0f; + float inertia = 0.0f; + + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 position1 = body->shape.vertexData.positions[i]; + unsigned int nextIndex = (((i + 1) < body->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 position2 = body->shape.vertexData.positions[nextIndex]; + + float cross = MathVector2CrossProduct(position1, position2); + float triangleArea = cross/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*PHYSAC_K*(position1.x + position2.x); + center.y += triangleArea*PHYSAC_K*(position1.y + position2.y); + + float intx2 = position1.x*position1.x + position2.x*position1.x + position2.x*position2.x; + float inty2 = position1.y*position1.y + position2.y*position1.y + position2.y*position2.y; + inertia += (0.25f*PHYSAC_K*cross)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (unsigned int i = 0; i < body->shape.vertexData.vertexCount; i++) + { + body->shape.vertexData.positions[i].x -= center.x; + body->shape.vertexData.positions[i].y -= center.y; + } + + body->mass = density*area; + body->inverseMass = ((body->mass != 0.0f) ? 1.0f/body->mass : 0.0f); + body->inertia = density*inertia; + body->inverseInertia = ((body->inertia != 0.0f) ? 1.0f/body->inertia : 0.0f); + body->staticFriction = 0.4f; + body->dynamicFriction = 0.2f; + body->restitution = 0.0f; + body->useGravity = true; + body->isGrounded = false; + body->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = body; + physicsBodiesCount++; + + TRACELOG("[PHYSAC] Physic body created successfully (id: %i)\n", body->id); + } + else TRACELOG("[PHYSAC] Physics body could not be created, PHYSAC_MAX_BODIES reached\n"); + + return body; +} + +// Adds a force to a physics body +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force) +{ + if (body != NULL) body->force = MathVector2Add(body->force, force); +} + +// Adds an angular force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount) +{ + if (body != NULL) body->torque += amount; +} + +// Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force) +{ + if (body != NULL) + { + if (body->shape.type == PHYSICS_POLYGON) + { + PhysicsVertexData vertexData = body->shape.vertexData; + bool collision = false; + + for (unsigned int i = 0; i < vertexData.vertexCount; i++) + { + Vector2 positionA = body->position; + Vector2 positionB = MathMatVector2Product(body->shape.transform, MathVector2Add(body->position, vertexData.positions[i])); + unsigned int nextIndex = (((i + 1) < vertexData.vertexCount) ? (i + 1) : 0); + Vector2 positionC = MathMatVector2Product(body->shape.transform, MathVector2Add(body->position, vertexData.positions[nextIndex])); + + // Check collision between each triangle + float alpha = ((positionB.y - positionC.y)*(position.x - positionC.x) + (positionC.x - positionB.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float beta = ((positionC.y - positionA.y)*(position.x - positionC.x) + (positionA.x - positionC.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0.0f) && (beta > 0.0f) & (gamma > 0.0f)) + { + collision = true; + break; + } + } + + if (collision) + { + int count = vertexData.vertexCount; + Vector2 bodyPos = body->position; + Vector2 *vertices = (Vector2 *)PHYSAC_MALLOC(sizeof(Vector2)*count); + Matrix2x2 trans = body->shape.transform; + for (int i = 0; i < count; i++) vertices[i] = vertexData.positions[i]; + + // Destroy shattered physics body + DestroyPhysicsBody(body); + + for (int i = 0; i < count; i++) + { + int nextIndex = (((i + 1) < count) ? (i + 1) : 0); + Vector2 center = MathTriangleBarycenter(vertices[i], vertices[nextIndex], PHYSAC_VECTOR_ZERO); + center = MathVector2Add(bodyPos, center); + Vector2 offset = MathVector2Subtract(center, bodyPos); + + PhysicsBody body = CreatePhysicsBodyPolygon(center, 10, 3, 10); // Create polygon physics body with relevant values + + PhysicsVertexData vertexData = { 0 }; + vertexData.vertexCount = 3; + + vertexData.positions[0] = MathVector2Subtract(vertices[i], offset); + vertexData.positions[1] = MathVector2Subtract(vertices[nextIndex], offset); + vertexData.positions[2] = MathVector2Subtract(position, center); + + // Separate vertices to avoid unnecessary physics collisions + vertexData.positions[0].x *= 0.95f; + vertexData.positions[0].y *= 0.95f; + vertexData.positions[1].x *= 0.95f; + vertexData.positions[1].y *= 0.95f; + vertexData.positions[2].x *= 0.95f; + vertexData.positions[2].y *= 0.95f; + + // Calculate polygon faces normals + for (unsigned int j = 0; j < vertexData.vertexCount; j++) + { + unsigned int nextVertex = (((j + 1) < vertexData.vertexCount) ? (j + 1) : 0); + Vector2 face = MathVector2Subtract(vertexData.positions[nextVertex], vertexData.positions[j]); + + vertexData.normals[j] = CLITERAL(Vector2){ face.y, -face.x }; + MathVector2Normalize(&vertexData.normals[j]); + } + + // Apply computed vertex data to new physics body shape + body->shape.vertexData = vertexData; + body->shape.transform = trans; + + // Calculate centroid and moment of inertia + center = PHYSAC_VECTOR_ZERO; + float area = 0.0f; + float inertia = 0.0f; + + for (unsigned int j = 0; j < body->shape.vertexData.vertexCount; j++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = body->shape.vertexData.positions[j]; + unsigned int nextVertex = (((j + 1) < body->shape.vertexData.vertexCount) ? (j + 1) : 0); + Vector2 p2 = body->shape.vertexData.positions[nextVertex]; + + float D = MathVector2CrossProduct(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*PHYSAC_K*(p1.x + p2.x); + center.y += triangleArea*PHYSAC_K*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*PHYSAC_K*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + body->mass = area; + body->inverseMass = ((body->mass != 0.0f) ? 1.0f/body->mass : 0.0f); + body->inertia = inertia; + body->inverseInertia = ((body->inertia != 0.0f) ? 1.0f/body->inertia : 0.0f); + + // Calculate explosion force direction + Vector2 pointA = body->position; + Vector2 pointB = MathVector2Subtract(vertexData.positions[1], vertexData.positions[0]); + pointB.x /= 2.0f; + pointB.y /= 2.0f; + Vector2 forceDirection = MathVector2Subtract(MathVector2Add(pointA, MathVector2Add(vertexData.positions[0], pointB)), body->position); + MathVector2Normalize(&forceDirection); + forceDirection.x *= force; + forceDirection.y *= force; + + // Apply force to new physics body + PhysicsAddForce(body, forceDirection); + } + + PHYSAC_FREE(vertices); + } + } + } + else TRACELOG("[PHYSAC] WARNING: PhysicsShatter: NULL physic body\n"); +} + +// Returns the current amount of created physics bodies +PHYSACDEF int GetPhysicsBodiesCount(void) +{ + return physicsBodiesCount; +} + +// Returns a physics body of the bodies pool at a specific index +PHYSACDEF PhysicsBody GetPhysicsBody(int index) +{ + PhysicsBody body = NULL; + + if (index < (int)physicsBodiesCount) + { + body = bodies[index]; + + if (body == NULL) TRACELOG("[PHYSAC] WARNING: GetPhysicsBody: NULL physic body\n"); + } + else TRACELOG("[PHYSAC] WARNING: Physic body index is out of bounds\n"); + + return body; +} + +// Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeType(int index) +{ + int result = -1; + + if (index < (int)physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + + if (body != NULL) result = body->shape.type; + else TRACELOG("[PHYSAC] WARNING: GetPhysicsShapeType: NULL physic body\n"); + } + else TRACELOG("[PHYSAC] WARNING: Physic body index is out of bounds\n"); + + return result; +} + +// Returns the amount of vertices of a physics body shape +PHYSACDEF int GetPhysicsShapeVerticesCount(int index) +{ + int result = 0; + + if (index < (int)physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: result = PHYSAC_DEFAULT_CIRCLE_VERTICES; break; + case PHYSICS_POLYGON: result = body->shape.vertexData.vertexCount; break; + default: break; + } + } + else TRACELOG("[PHYSAC] WARNING: GetPhysicsShapeVerticesCount: NULL physic body\n"); + } + else TRACELOG("[PHYSAC] WARNING: Physic body index is out of bounds\n"); + + return result; +} + +// Returns transformed position of a body shape (body position + vertex transformed position) +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex) +{ + Vector2 position = { 0.0f, 0.0f }; + + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: + { + position.x = body->position.x + cosf(360.0f/PHYSAC_DEFAULT_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + position.y = body->position.y + sinf(360.0f/PHYSAC_DEFAULT_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + } break; + case PHYSICS_POLYGON: + { + PhysicsVertexData vertexData = body->shape.vertexData; + position = MathVector2Add(body->position, MathMatVector2Product(body->shape.transform, vertexData.positions[vertex])); + } break; + default: break; + } + } + else TRACELOG("[PHYSAC] WARNING: GetPhysicsShapeVertex: NULL physic body\n"); + + return position; +} + +// Sets physics body shape transform based on radians parameter +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians) +{ + if (body != NULL) + { + body->orient = radians; + + if (body->shape.type == PHYSICS_POLYGON) body->shape.transform = MathMatFromRadians(radians); + } +} + +// Unitializes and destroys a physics body +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body) +{ + if (body != NULL) + { + int id = body->id; + int index = -1; + + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + if (bodies[i]->id == id) + { + index = i; + break; + } + } + + if (index == -1) + { + TRACELOG("[PHYSAC] WARNING: Requested body (id: %i) can not be found\n", id); + return; // Prevent access to index -1 + } + + // Free body allocated memory + PHYSAC_FREE(body); + usedMemory -= sizeof(PhysicsBodyData); + bodies[index] = NULL; + + // Reorder physics bodies pointers array and its catched index + for (unsigned int i = index; i < physicsBodiesCount; i++) + { + if ((i + 1) < physicsBodiesCount) bodies[i] = bodies[i + 1]; + } + + // Update physics bodies count + physicsBodiesCount--; + + TRACELOG("[PHYSAC] Physic body destroyed successfully (id: %i)\n", id); + } + else TRACELOG("[PHYSAC] WARNING: DestroyPhysicsBody: NULL physic body\n"); +} + +// Destroys created physics bodies and manifolds and resets global values +PHYSACDEF void ResetPhysics(void) +{ + if (physicsBodiesCount > 0) + { + // Unitialize physics bodies dynamic memory allocations + for (int i = physicsBodiesCount - 1; i >= 0; i--) + { + PhysicsBody body = bodies[i]; + + if (body != NULL) + { + PHYSAC_FREE(body); + bodies[i] = NULL; + usedMemory -= sizeof(PhysicsBodyData); + } + } + + physicsBodiesCount = 0; + } + + if (physicsManifoldsCount > 0) + { + // Unitialize physics manifolds dynamic memory allocations + for (int i = physicsManifoldsCount - 1; i >= 0; i--) + { + PhysicsManifold manifold = contacts[i]; + + if (manifold != NULL) + { + PHYSAC_FREE(manifold); + contacts[i] = NULL; + usedMemory -= sizeof(PhysicsManifoldData); + } + } + + physicsManifoldsCount = 0; + } + + TRACELOG("[PHYSAC] Physics module reseted successfully\n"); +} + +// Unitializes physics pointers and exits physics loop thread +PHYSACDEF void ClosePhysics(void) +{ + // Unitialize physics manifolds dynamic memory allocations + if (physicsManifoldsCount > 0) + { + for (unsigned int i = physicsManifoldsCount - 1; i >= 0; i--) + DestroyPhysicsManifold(contacts[i]); + } + + // Unitialize physics bodies dynamic memory allocations + if (physicsBodiesCount > 0) + { + for (unsigned int i = physicsBodiesCount - 1; i >= 0; i--) + DestroyPhysicsBody(bodies[i]); + } + + // Trace log info + if ((physicsBodiesCount > 0) || (usedMemory != 0)) + { + TRACELOG("[PHYSAC] WARNING: Physics module closed with unallocated bodies (BODIES: %i, MEMORY: %i bytes)\n", physicsBodiesCount, usedMemory); + } + else if ((physicsManifoldsCount > 0) || (usedMemory != 0)) + { + TRACELOG("[PHYSAC] WARNING: Pysics module closed with unallocated manifolds (MANIFOLDS: %i, MEMORY: %i bytes)\n", physicsManifoldsCount, usedMemory); + } + else TRACELOG("[PHYSAC] Physics module closed successfully\n"); +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Finds a valid index for a new physics body initialization +static int FindAvailableBodyIndex() +{ + int index = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (unsigned int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == (int)i) + { + index = (int)i; + break; + } + } + + return index; +} + +// Creates a default polygon shape with max vertex distance from polygon pivot +static PhysicsVertexData CreateDefaultPolygon(float radius, int sides) +{ + PhysicsVertexData data = { 0 }; + data.vertexCount = sides; + + // Calculate polygon vertices positions + for (unsigned int i = 0; i < data.vertexCount; i++) + { + data.positions[i].x = (float)cosf(360.0f/sides*i*PHYSAC_DEG2RAD)*radius; + data.positions[i].y = (float)sinf(360.0f/sides*i*PHYSAC_DEG2RAD)*radius; + } + + // Calculate polygon faces normals + for (int i = 0; i < (int)data.vertexCount; i++) + { + int nextIndex = (((i + 1) < sides) ? (i + 1) : 0); + Vector2 face = MathVector2Subtract(data.positions[nextIndex], data.positions[i]); + + data.normals[i] = CLITERAL(Vector2){ face.y, -face.x }; + MathVector2Normalize(&data.normals[i]); + } + + return data; +} + +// Creates a rectangle polygon shape based on a min and max positions +static PhysicsVertexData CreateRectanglePolygon(Vector2 pos, Vector2 size) +{ + PhysicsVertexData data = { 0 }; + data.vertexCount = 4; + + // Calculate polygon vertices positions + data.positions[0] = CLITERAL(Vector2){ pos.x + size.x/2, pos.y - size.y/2 }; + data.positions[1] = CLITERAL(Vector2){ pos.x + size.x/2, pos.y + size.y/2 }; + data.positions[2] = CLITERAL(Vector2){ pos.x - size.x/2, pos.y + size.y/2 }; + data.positions[3] = CLITERAL(Vector2){ pos.x - size.x/2, pos.y - size.y/2 }; + + // Calculate polygon faces normals + for (unsigned int i = 0; i < data.vertexCount; i++) + { + int nextIndex = (((i + 1) < data.vertexCount) ? (i + 1) : 0); + Vector2 face = MathVector2Subtract(data.positions[nextIndex], data.positions[i]); + + data.normals[i] = CLITERAL(Vector2){ face.y, -face.x }; + MathVector2Normalize(&data.normals[i]); + } + + return data; +} + +// Update physics step (dynamics, collisions and position corrections) +void UpdatePhysicsStep(void) +{ + // Clear previous generated collisions information + for (int i = (int)physicsManifoldsCount - 1; i >= 0; i--) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) DestroyPhysicsManifold(manifold); + } + + // Reset physics bodies grounded state + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + body->isGrounded = false; + } + + // Generate new collision information + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody bodyA = bodies[i]; + + if (bodyA != NULL) + { + for (unsigned int j = i + 1; j < physicsBodiesCount; j++) + { + PhysicsBody bodyB = bodies[j]; + + if (bodyB != NULL) + { + if ((bodyA->inverseMass == 0) && (bodyB->inverseMass == 0)) continue; + + PhysicsManifold manifold = CreatePhysicsManifold(bodyA, bodyB); + SolvePhysicsManifold(manifold); + + if (manifold->contactsCount > 0) + { + // Create a new manifold with same information as previously solved manifold and add it to the manifolds pool last slot + PhysicsManifold manifold = CreatePhysicsManifold(bodyA, bodyB); + manifold->penetration = manifold->penetration; + manifold->normal = manifold->normal; + manifold->contacts[0] = manifold->contacts[0]; + manifold->contacts[1] = manifold->contacts[1]; + manifold->contactsCount = manifold->contactsCount; + manifold->restitution = manifold->restitution; + manifold->dynamicFriction = manifold->dynamicFriction; + manifold->staticFriction = manifold->staticFriction; + } + } + } + } + } + + // Integrate forces to physics bodies + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsForces(body); + } + + // Initialize physics manifolds to solve collisions + for (unsigned int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) InitializePhysicsManifolds(manifold); + } + + // Integrate physics collisions impulses to solve collisions + for (unsigned int i = 0; i < PHYSAC_COLLISION_ITERATIONS; i++) + { + for (unsigned int j = 0; j < physicsManifoldsCount; j++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) IntegratePhysicsImpulses(manifold); + } + } + + // Integrate velocity to physics bodies + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsVelocity(body); + } + + // Correct physics bodies positions based on manifolds collision information + for (unsigned int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) CorrectPhysicsPositions(manifold); + } + + // Clear physics bodies forces + for (unsigned int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) + { + body->force = PHYSAC_VECTOR_ZERO; + body->torque = 0.0f; + } + } +} + +// Update physics system +// Physics steps are launched at a fixed time step if enabled +PHYSACDEF void UpdatePhysics(void) +{ +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) + static double deltaTimeAccumulator = 0.0; + + // Calculate current time (ms) + currentTime = GetCurrentTime(); + + // Calculate current delta time (ms) + const double delta = currentTime - startTime; + + // Store the time elapsed since the last frame began + deltaTimeAccumulator += delta; + + // Fixed time stepping loop + while (deltaTimeAccumulator >= deltaTime) + { + UpdatePhysicsStep(); + deltaTimeAccumulator -= deltaTime; + } + + // Record the starting of this frame + startTime = currentTime; +#else + UpdatePhysicsStep(); +#endif +} + +PHYSACDEF void SetPhysicsTimeStep(double delta) +{ + deltaTime = delta; +} + +// Finds a valid index for a new manifold initialization +static int FindAvailableManifoldIndex() +{ + int index = -1; + for (int i = 0; i < PHYSAC_MAX_MANIFOLDS; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (unsigned int k = 0; k < physicsManifoldsCount; k++) + { + if (contacts[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + index = i; + break; + } + } + + return index; +} + +// Creates a new physics manifold to solve collision +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b) +{ + PhysicsManifold manifold = (PhysicsManifold)PHYSAC_MALLOC(sizeof(PhysicsManifoldData)); + usedMemory += sizeof(PhysicsManifoldData); + + int id = FindAvailableManifoldIndex(); + if (id != -1) + { + // Initialize new manifold with generic values + manifold->id = id; + manifold->bodyA = a; + manifold->bodyB = b; + manifold->penetration = 0; + manifold->normal = PHYSAC_VECTOR_ZERO; + manifold->contacts[0] = PHYSAC_VECTOR_ZERO; + manifold->contacts[1] = PHYSAC_VECTOR_ZERO; + manifold->contactsCount = 0; + manifold->restitution = 0.0f; + manifold->dynamicFriction = 0.0f; + manifold->staticFriction = 0.0f; + + // Add new body to bodies pointers array and update bodies count + contacts[physicsManifoldsCount] = manifold; + physicsManifoldsCount++; + } + else TRACELOG("[PHYSAC] Physic manifold could not be created, PHYSAC_MAX_MANIFOLDS reached\n"); + + return manifold; +} + +// Unitializes and destroys a physics manifold +static void DestroyPhysicsManifold(PhysicsManifold manifold) +{ + if (manifold != NULL) + { + int id = manifold->id; + int index = -1; + + for (unsigned int i = 0; i < physicsManifoldsCount; i++) + { + if (contacts[i]->id == id) + { + index = i; + break; + } + } + + if (index == -1) return; // Prevent access to index -1 + + // Free manifold allocated memory + PHYSAC_FREE(manifold); + usedMemory -= sizeof(PhysicsManifoldData); + contacts[index] = NULL; + + // Reorder physics manifolds pointers array and its catched index + for (unsigned int i = index; i < physicsManifoldsCount; i++) + { + if ((i + 1) < physicsManifoldsCount) contacts[i] = contacts[i + 1]; + } + + // Update physics manifolds count + physicsManifoldsCount--; + } + else TRACELOG("[PHYSAC] WARNING: DestroyPhysicsManifold: NULL physic manifold\n"); +} + +// Solves a created physics manifold between two physics bodies +static void SolvePhysicsManifold(PhysicsManifold manifold) +{ + switch (manifold->bodyA->shape.type) + { + case PHYSICS_CIRCLE: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolveCircleToCircle(manifold); break; + case PHYSICS_POLYGON: SolveCircleToPolygon(manifold); break; + default: break; + } + } break; + case PHYSICS_POLYGON: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolvePolygonToCircle(manifold); break; + case PHYSICS_POLYGON: SolvePolygonToPolygon(manifold); break; + default: break; + } + } break; + default: break; + } + + // Update physics body grounded state if normal direction is down and grounded state is not set yet in previous manifolds + if (!manifold->bodyB->isGrounded) manifold->bodyB->isGrounded = (manifold->normal.y < 0); +} + +// Solves collision between two circle shape physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + // Calculate translational vector, which is normal + Vector2 normal = MathVector2Subtract(bodyB->position, bodyA->position); + + float distSqr = MathVector2SqrLen(normal); + float radius = bodyA->shape.radius + bodyB->shape.radius; + + // Check if circles are not in contact + if (distSqr >= radius*radius) + { + manifold->contactsCount = 0; + return; + } + + float distance = sqrtf(distSqr); + manifold->contactsCount = 1; + + if (distance == 0.0f) + { + manifold->penetration = bodyA->shape.radius; + manifold->normal = CLITERAL(Vector2){ 1.0f, 0.0f }; + manifold->contacts[0] = bodyA->position; + } + else + { + manifold->penetration = radius - distance; + manifold->normal = CLITERAL(Vector2){ normal.x/distance, normal.y/distance }; // Faster than using MathVector2Normalize() due to sqrt is already performed + manifold->contacts[0] = CLITERAL(Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + } + + // Update physics body grounded state if normal direction is down + if (!bodyA->isGrounded) bodyA->isGrounded = (manifold->normal.y < 0); +} + +// Solves collision between a circle to a polygon shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + manifold->contactsCount = 0; + + // Transform circle center to polygon transform space + Vector2 center = bodyA->position; + center = MathMatVector2Product(MathMatTranspose(bodyB->shape.transform), MathVector2Subtract(center, bodyB->position)); + + // Find edge with minimum penetration + // It is the same concept as using support points in SolvePolygonToPolygon + float separation = -PHYSAC_FLT_MAX; + int faceNormal = 0; + PhysicsVertexData vertexData = bodyB->shape.vertexData; + + for (unsigned int i = 0; i < vertexData.vertexCount; i++) + { + float currentSeparation = MathVector2DotProduct(vertexData.normals[i], MathVector2Subtract(center, vertexData.positions[i])); + + if (currentSeparation > bodyA->shape.radius) return; + + if (currentSeparation > separation) + { + separation = currentSeparation; + faceNormal = i; + } + } + + // Grab face's vertices + Vector2 v1 = vertexData.positions[faceNormal]; + int nextIndex = (((faceNormal + 1) < (int)vertexData.vertexCount) ? (faceNormal + 1) : 0); + Vector2 v2 = vertexData.positions[nextIndex]; + + // Check to see if center is within polygon + if (separation < PHYSAC_EPSILON) + { + manifold->contactsCount = 1; + Vector2 normal = MathMatVector2Product(bodyB->shape.transform, vertexData.normals[faceNormal]); + manifold->normal = CLITERAL(Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = CLITERAL(Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->penetration = bodyA->shape.radius; + return; + } + + // Determine which voronoi region of the edge center of circle lies within + float dot1 = MathVector2DotProduct(MathVector2Subtract(center, v1), MathVector2Subtract(v2, v1)); + float dot2 = MathVector2DotProduct(MathVector2Subtract(center, v2), MathVector2Subtract(v1, v2)); + manifold->penetration = bodyA->shape.radius - separation; + + if (dot1 <= 0.0f) // Closest to v1 + { + if (MathVector2SqrDistance(center, v1) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = MathVector2Subtract(v1, center); + normal = MathMatVector2Product(bodyB->shape.transform, normal); + MathVector2Normalize(&normal); + manifold->normal = normal; + v1 = MathMatVector2Product(bodyB->shape.transform, v1); + v1 = MathVector2Add(v1, bodyB->position); + manifold->contacts[0] = v1; + } + else if (dot2 <= 0.0f) // Closest to v2 + { + if (MathVector2SqrDistance(center, v2) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = MathVector2Subtract(v2, center); + v2 = MathMatVector2Product(bodyB->shape.transform, v2); + v2 = MathVector2Add(v2, bodyB->position); + manifold->contacts[0] = v2; + normal = MathMatVector2Product(bodyB->shape.transform, normal); + MathVector2Normalize(&normal); + manifold->normal = normal; + } + else // Closest to face + { + Vector2 normal = vertexData.normals[faceNormal]; + + if (MathVector2DotProduct(MathVector2Subtract(center, v1), normal) > bodyA->shape.radius) return; + + normal = MathMatVector2Product(bodyB->shape.transform, normal); + manifold->normal = CLITERAL(Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = CLITERAL(Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->contactsCount = 1; + } +} + +// Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + manifold->bodyA = bodyB; + manifold->bodyB = bodyA; + SolveCircleToPolygon(manifold); + + manifold->normal.x *= -1.0f; + manifold->normal.y *= -1.0f; +} + +// Solves collision between two polygons shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold) +{ + if ((manifold->bodyA == NULL) || (manifold->bodyB == NULL)) return; + + PhysicsShape bodyA = manifold->bodyA->shape; + PhysicsShape bodyB = manifold->bodyB->shape; + manifold->contactsCount = 0; + + // Check for separating axis with A shape's face planes + int faceA = 0; + float penetrationA = FindAxisLeastPenetration(&faceA, bodyA, bodyB); + if (penetrationA >= 0.0f) return; + + // Check for separating axis with B shape's face planes + int faceB = 0; + float penetrationB = FindAxisLeastPenetration(&faceB, bodyB, bodyA); + if (penetrationB >= 0.0f) return; + + int referenceIndex = 0; + bool flip = false; // Always point from A shape to B shape + + PhysicsShape refPoly; // Reference + PhysicsShape incPoly; // Incident + + // Determine which shape contains reference face + // Checking bias range for penetration + if (penetrationA >= (penetrationB*0.95f + penetrationA*0.01f)) + { + refPoly = bodyA; + incPoly = bodyB; + referenceIndex = faceA; + } + else + { + refPoly = bodyB; + incPoly = bodyA; + referenceIndex = faceB; + flip = true; + } + + // World space incident face + Vector2 incidentFace[2]; + FindIncidentFace(&incidentFace[0], &incidentFace[1], refPoly, incPoly, referenceIndex); + + // Setup reference face vertices + PhysicsVertexData refData = refPoly.vertexData; + Vector2 v1 = refData.positions[referenceIndex]; + referenceIndex = (((referenceIndex + 1) < (int)refData.vertexCount) ? (referenceIndex + 1) : 0); + Vector2 v2 = refData.positions[referenceIndex]; + + // Transform vertices to world space + v1 = MathMatVector2Product(refPoly.transform, v1); + v1 = MathVector2Add(v1, refPoly.body->position); + v2 = MathMatVector2Product(refPoly.transform, v2); + v2 = MathVector2Add(v2, refPoly.body->position); + + // Calculate reference face side normal in world space + Vector2 sidePlaneNormal = MathVector2Subtract(v2, v1); + MathVector2Normalize(&sidePlaneNormal); + + // Orthogonalize + Vector2 refFaceNormal = { sidePlaneNormal.y, -sidePlaneNormal.x }; + float refC = MathVector2DotProduct(refFaceNormal, v1); + float negSide = MathVector2DotProduct(sidePlaneNormal, v1)*-1; + float posSide = MathVector2DotProduct(sidePlaneNormal, v2); + + // MathVector2Clip incident face to reference face side planes (due to floating point error, possible to not have required points + if (MathVector2Clip(CLITERAL(Vector2){ -sidePlaneNormal.x, -sidePlaneNormal.y }, &incidentFace[0], &incidentFace[1], negSide) < 2) return; + if (MathVector2Clip(sidePlaneNormal, &incidentFace[0], &incidentFace[1], posSide) < 2) return; + + // Flip normal if required + manifold->normal = (flip ? CLITERAL(Vector2){ -refFaceNormal.x, -refFaceNormal.y } : refFaceNormal); + + // Keep points behind reference face + int currentPoint = 0; // MathVector2Clipped points behind reference face + float separation = MathVector2DotProduct(refFaceNormal, incidentFace[0]) - refC; + if (separation <= 0.0f) + { + manifold->contacts[currentPoint] = incidentFace[0]; + manifold->penetration = -separation; + currentPoint++; + } + else manifold->penetration = 0.0f; + + separation = MathVector2DotProduct(refFaceNormal, incidentFace[1]) - refC; + + if (separation <= 0.0f) + { + manifold->contacts[currentPoint] = incidentFace[1]; + manifold->penetration += -separation; + currentPoint++; + + // Calculate total penetration average + manifold->penetration /= currentPoint; + } + + manifold->contactsCount = currentPoint; +} + +// Integrates physics forces into velocity +static void IntegratePhysicsForces(PhysicsBody body) +{ + if ((body == NULL) || (body->inverseMass == 0.0f) || !body->enabled) return; + + body->velocity.x += (float)((body->force.x*body->inverseMass)*(deltaTime/2.0)); + body->velocity.y += (float)((body->force.y*body->inverseMass)*(deltaTime/2.0)); + + if (body->useGravity) + { + body->velocity.x += (float)(gravityForce.x*(deltaTime/1000/2.0)); + body->velocity.y += (float)(gravityForce.y*(deltaTime/1000/2.0)); + } + + if (!body->freezeOrient) body->angularVelocity += (float)(body->torque*body->inverseInertia*(deltaTime/2.0)); +} + +// Initializes physics manifolds to solve collisions +static void InitializePhysicsManifolds(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + // Calculate average restitution, static and dynamic friction + manifold->restitution = sqrtf(bodyA->restitution*bodyB->restitution); + manifold->staticFriction = sqrtf(bodyA->staticFriction*bodyB->staticFriction); + manifold->dynamicFriction = sqrtf(bodyA->dynamicFriction*bodyB->dynamicFriction); + + for (unsigned int i = 0; i < manifold->contactsCount; i++) + { + // Caculate radius from center of mass to contact + Vector2 radiusA = MathVector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = MathVector2Subtract(manifold->contacts[i], bodyB->position); + + Vector2 crossA = MathVector2Product(radiusA, bodyA->angularVelocity); + Vector2 crossB = MathVector2Product(radiusB, bodyB->angularVelocity); + + Vector2 radiusV = { 0.0f, 0.0f }; + radiusV.x = bodyB->velocity.x + crossB.x - bodyA->velocity.x - crossA.x; + radiusV.y = bodyB->velocity.y + crossB.y - bodyA->velocity.y - crossA.y; + + // Determine if we should perform a resting collision or not; + // The idea is if the only thing moving this object is gravity, then the collision should be performed without any restitution + if (MathVector2SqrLen(radiusV) < (MathVector2SqrLen(CLITERAL(Vector2){ (float)(gravityForce.x*deltaTime/1000), (float)(gravityForce.y*deltaTime/1000) }) + PHYSAC_EPSILON)) manifold->restitution = 0; + } +} + +// Integrates physics collisions impulses to solve collisions +static void IntegratePhysicsImpulses(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + // Early out and positional correct if both objects have infinite mass + if (fabs(bodyA->inverseMass + bodyB->inverseMass) <= PHYSAC_EPSILON) + { + bodyA->velocity = PHYSAC_VECTOR_ZERO; + bodyB->velocity = PHYSAC_VECTOR_ZERO; + return; + } + + for (unsigned int i = 0; i < manifold->contactsCount; i++) + { + // Calculate radius from center of mass to contact + Vector2 radiusA = MathVector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = MathVector2Subtract(manifold->contacts[i], bodyB->position); + + // Calculate relative velocity + Vector2 radiusV = { 0.0f, 0.0f }; + radiusV.x = bodyB->velocity.x + MathVector2Product(radiusB, bodyB->angularVelocity).x - bodyA->velocity.x - MathVector2Product(radiusA, bodyA->angularVelocity).x; + radiusV.y = bodyB->velocity.y + MathVector2Product(radiusB, bodyB->angularVelocity).y - bodyA->velocity.y - MathVector2Product(radiusA, bodyA->angularVelocity).y; + + // Relative velocity along the normal + float contactVelocity = MathVector2DotProduct(radiusV, manifold->normal); + + // Do not resolve if velocities are separating + if (contactVelocity > 0.0f) return; + + float raCrossN = MathVector2CrossProduct(radiusA, manifold->normal); + float rbCrossN = MathVector2CrossProduct(radiusB, manifold->normal); + + float inverseMassSum = bodyA->inverseMass + bodyB->inverseMass + (raCrossN*raCrossN)*bodyA->inverseInertia + (rbCrossN*rbCrossN)*bodyB->inverseInertia; + + // Calculate impulse scalar value + float impulse = -(1.0f + manifold->restitution)*contactVelocity; + impulse /= inverseMassSum; + impulse /= (float)manifold->contactsCount; + + // Apply impulse to each physics body + Vector2 impulseV = { manifold->normal.x*impulse, manifold->normal.y*impulse }; + + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-impulseV.x); + bodyA->velocity.y += bodyA->inverseMass*(-impulseV.y); + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathVector2CrossProduct(radiusA, CLITERAL(Vector2){ -impulseV.x, -impulseV.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(impulseV.x); + bodyB->velocity.y += bodyB->inverseMass*(impulseV.y); + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathVector2CrossProduct(radiusB, impulseV); + } + + // Apply friction impulse to each physics body + radiusV.x = bodyB->velocity.x + MathVector2Product(radiusB, bodyB->angularVelocity).x - bodyA->velocity.x - MathVector2Product(radiusA, bodyA->angularVelocity).x; + radiusV.y = bodyB->velocity.y + MathVector2Product(radiusB, bodyB->angularVelocity).y - bodyA->velocity.y - MathVector2Product(radiusA, bodyA->angularVelocity).y; + + Vector2 tangent = { radiusV.x - (manifold->normal.x*MathVector2DotProduct(radiusV, manifold->normal)), radiusV.y - (manifold->normal.y*MathVector2DotProduct(radiusV, manifold->normal)) }; + MathVector2Normalize(&tangent); + + // Calculate impulse tangent magnitude + float impulseTangent = -MathVector2DotProduct(radiusV, tangent); + impulseTangent /= inverseMassSum; + impulseTangent /= (float)manifold->contactsCount; + + float absImpulseTangent = (float)fabs(impulseTangent); + + // Don't apply tiny friction impulses + if (absImpulseTangent <= PHYSAC_EPSILON) return; + + // Apply coulumb's law + Vector2 tangentImpulse = { 0.0f, 0.0f }; + if (absImpulseTangent < impulse*manifold->staticFriction) tangentImpulse = CLITERAL(Vector2){ tangent.x*impulseTangent, tangent.y*impulseTangent }; + else tangentImpulse = CLITERAL(Vector2){ tangent.x*-impulse*manifold->dynamicFriction, tangent.y*-impulse*manifold->dynamicFriction }; + + // Apply friction impulse + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-tangentImpulse.x); + bodyA->velocity.y += bodyA->inverseMass*(-tangentImpulse.y); + + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathVector2CrossProduct(radiusA, CLITERAL(Vector2){ -tangentImpulse.x, -tangentImpulse.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(tangentImpulse.x); + bodyB->velocity.y += bodyB->inverseMass*(tangentImpulse.y); + + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathVector2CrossProduct(radiusB, tangentImpulse); + } + } +} + +// Integrates physics velocity into position and forces +static void IntegratePhysicsVelocity(PhysicsBody body) +{ + if ((body == NULL) ||!body->enabled) return; + + body->position.x += (float)(body->velocity.x*deltaTime); + body->position.y += (float)(body->velocity.y*deltaTime); + + if (!body->freezeOrient) body->orient += (float)(body->angularVelocity*deltaTime); + body->shape.transform = MathMatFromRadians(body->orient); + + IntegratePhysicsForces(body); +} + +// Corrects physics bodies positions based on manifolds collision information +static void CorrectPhysicsPositions(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + if ((bodyA == NULL) || (bodyB == NULL)) return; + + Vector2 correction = { 0.0f, 0.0f }; + correction.x = (PHYSAC_MAX(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0.0f)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.x*PHYSAC_PENETRATION_CORRECTION; + correction.y = (PHYSAC_MAX(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0.0f)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.y*PHYSAC_PENETRATION_CORRECTION; + + if (bodyA->enabled) + { + bodyA->position.x -= correction.x*bodyA->inverseMass; + bodyA->position.y -= correction.y*bodyA->inverseMass; + } + + if (bodyB->enabled) + { + bodyB->position.x += correction.x*bodyB->inverseMass; + bodyB->position.y += correction.y*bodyB->inverseMass; + } +} + +// Returns the extreme point along a direction within a polygon +static Vector2 GetSupport(PhysicsShape shape, Vector2 dir) +{ + float bestProjection = -PHYSAC_FLT_MAX; + Vector2 bestVertex = { 0.0f, 0.0f }; + PhysicsVertexData data = shape.vertexData; + + for (unsigned int i = 0; i < data.vertexCount; i++) + { + Vector2 vertex = data.positions[i]; + float projection = MathVector2DotProduct(vertex, dir); + + if (projection > bestProjection) + { + bestVertex = vertex; + bestProjection = projection; + } + } + + return bestVertex; +} + +// Finds polygon shapes axis least penetration +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB) +{ + float bestDistance = -PHYSAC_FLT_MAX; + int bestIndex = 0; + + PhysicsVertexData dataA = shapeA.vertexData; + //PhysicsVertexData dataB = shapeB.vertexData; + + for (unsigned int i = 0; i < dataA.vertexCount; i++) + { + // Retrieve a face normal from A shape + Vector2 normal = dataA.normals[i]; + Vector2 transNormal = MathMatVector2Product(shapeA.transform, normal); + + // Transform face normal into B shape's model space + Matrix2x2 buT = MathMatTranspose(shapeB.transform); + normal = MathMatVector2Product(buT, transNormal); + + // Retrieve support point from B shape along -n + Vector2 support = GetSupport(shapeB, CLITERAL(Vector2){ -normal.x, -normal.y }); + + // Retrieve vertex on face from A shape, transform into B shape's model space + Vector2 vertex = dataA.positions[i]; + vertex = MathMatVector2Product(shapeA.transform, vertex); + vertex = MathVector2Add(vertex, shapeA.body->position); + vertex = MathVector2Subtract(vertex, shapeB.body->position); + vertex = MathMatVector2Product(buT, vertex); + + // Compute penetration distance in B shape's model space + float distance = MathVector2DotProduct(normal, MathVector2Subtract(support, vertex)); + + // Store greatest distance + if (distance > bestDistance) + { + bestDistance = distance; + bestIndex = i; + } + } + + *faceIndex = bestIndex; + return bestDistance; +} + +// Finds two polygon shapes incident face +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index) +{ + PhysicsVertexData refData = ref.vertexData; + PhysicsVertexData incData = inc.vertexData; + + Vector2 referenceNormal = refData.normals[index]; + + // Calculate normal in incident's frame of reference + referenceNormal = MathMatVector2Product(ref.transform, referenceNormal); // To world space + referenceNormal = MathMatVector2Product(MathMatTranspose(inc.transform), referenceNormal); // To incident's model space + + // Find most anti-normal face on polygon + int incidentFace = 0; + float minDot = PHYSAC_FLT_MAX; + + for (unsigned int i = 0; i < incData.vertexCount; i++) + { + float dot = MathVector2DotProduct(referenceNormal, incData.normals[i]); + + if (dot < minDot) + { + minDot = dot; + incidentFace = i; + } + } + + // Assign face vertices for incident face + *v0 = MathMatVector2Product(inc.transform, incData.positions[incidentFace]); + *v0 = MathVector2Add(*v0, inc.body->position); + incidentFace = (((incidentFace + 1) < (int)incData.vertexCount) ? (incidentFace + 1) : 0); + *v1 = MathMatVector2Product(inc.transform, incData.positions[incidentFace]); + *v1 = MathVector2Add(*v1, inc.body->position); +} + +// Returns clipping value based on a normal and two faces +static int MathVector2Clip(Vector2 normal, Vector2 *faceA, Vector2 *faceB, float clip) +{ + int sp = 0; + Vector2 out[2] = { *faceA, *faceB }; + + // Retrieve distances from each endpoint to the line + float distanceA = MathVector2DotProduct(normal, *faceA) - clip; + float distanceB = MathVector2DotProduct(normal, *faceB) - clip; + + // If negative (behind plane) + if (distanceA <= 0.0f) out[sp++] = *faceA; + if (distanceB <= 0.0f) out[sp++] = *faceB; + + // If the points are on different sides of the plane + if ((distanceA*distanceB) < 0.0f) + { + // Push intersection point + float alpha = distanceA/(distanceA - distanceB); + out[sp] = *faceA; + Vector2 delta = MathVector2Subtract(*faceB, *faceA); + delta.x *= alpha; + delta.y *= alpha; + out[sp] = MathVector2Add(out[sp], delta); + sp++; + } + + // Assign the new converted values + *faceA = out[0]; + *faceB = out[1]; + + return sp; +} + +// Returns the barycenter of a triangle given by 3 points +static Vector2 MathTriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3) +{ + Vector2 result = { 0.0f, 0.0f }; + + result.x = (v1.x + v2.x + v3.x)/3; + result.y = (v1.y + v2.y + v3.y)/3; + + return result; +} + +#if !defined(PHYSAC_AVOID_TIMMING_SYSTEM) +// Initializes hi-resolution MONOTONIC timer +static void InitTimer(void) +{ +#if defined(_WIN32) + QueryPerformanceFrequency((unsigned long long int *) &frequency); +#endif + +#if defined(__EMSCRIPTEN__) || defined(__linux__) + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) frequency = 1000000000; +#endif + +#if defined(__APPLE__) + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + frequency = (timebase.denom*1e9)/timebase.numer; +#endif + + baseClockTicks = (double)GetClockTicks(); // Get MONOTONIC clock time offset + startTime = GetCurrentTime(); // Get current time in milliseconds +} + +// Get hi-res MONOTONIC time measure in clock ticks +static unsigned long long int GetClockTicks(void) +{ + unsigned long long int value = 0; + +#if defined(_WIN32) + QueryPerformanceCounter((unsigned long long int *) &value); +#endif + +#if defined(__linux__) + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + value = (unsigned long long int)now.tv_sec*(unsigned long long int)1000000000 + (unsigned long long int)now.tv_nsec; +#endif + +#if defined(__APPLE__) + value = mach_absolute_time(); +#endif + + return value; +} + +// Get current time in milliseconds +static double GetCurrentTime(void) +{ + return (double)(GetClockTicks() - baseClockTicks)/frequency*1000; +} +#endif // !PHYSAC_AVOID_TIMMING_SYSTEM + + +// Returns the cross product of a vector and a value +static inline Vector2 MathVector2Product(Vector2 vector, float value) +{ + Vector2 result = { -value*vector.y, value*vector.x }; + return result; +} + +// Returns the cross product of two vectors +static inline float MathVector2CrossProduct(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.y - v1.y*v2.x); +} + +// Returns the len square root of a vector +static inline float MathVector2SqrLen(Vector2 vector) +{ + return (vector.x*vector.x + vector.y*vector.y); +} + +// Returns the dot product of two vectors +static inline float MathVector2DotProduct(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.x + v1.y*v2.y); +} + +// Returns the square root of distance between two vectors +static inline float MathVector2SqrDistance(Vector2 v1, Vector2 v2) +{ + Vector2 dir = MathVector2Subtract(v1, v2); + return MathVector2DotProduct(dir, dir); +} + +// Returns the normalized values of a vector +static void MathVector2Normalize(Vector2 *vector) +{ + float length, ilength; + + Vector2 aux = *vector; + length = sqrtf(aux.x*aux.x + aux.y*aux.y); + + if (length == 0) length = 1.0f; + + ilength = 1.0f/length; + + vector->x *= ilength; + vector->y *= ilength; +} + +// Returns the sum of two given vectors +static inline Vector2 MathVector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + return result; +} + +// Returns the subtract of two given vectors +static inline Vector2 MathVector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + return result; +} + +// Creates a matrix 2x2 from a given radians value +static Matrix2x2 MathMatFromRadians(float radians) +{ + float cos = cosf(radians); + float sin = sinf(radians); + + Matrix2x2 result = { cos, -sin, sin, cos }; + return result; +} + +// Returns the transpose of a given matrix 2x2 +static inline Matrix2x2 MathMatTranspose(Matrix2x2 matrix) +{ + Matrix2x2 result = { matrix.m00, matrix.m10, matrix.m01, matrix.m11 }; + return result; +} + +// Multiplies a vector by a matrix 2x2 +static inline Vector2 MathMatVector2Product(Matrix2x2 matrix, Vector2 vector) +{ + Vector2 result = { matrix.m00*vector.x + matrix.m01*vector.y, matrix.m10*vector.x + matrix.m11*vector.y }; + return result; +} + +#endif // PHYSAC_IMPLEMENTATION diff --git a/raylib_pi4_test/raudio.c b/raylib_pi4_test/raudio.c new file mode 100644 index 0000000..2b6b0a1 --- /dev/null +++ b/raylib_pi4_test/raudio.c @@ -0,0 +1,2414 @@ +/********************************************************************************************** +* +* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio +* +* FEATURES: +* - Manage audio device (init/close) +* - Manage raw audio context +* - Manage mixing channels +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* +* CONFIGURATION: +* +* #define RAUDIO_STANDALONE +* Define to use the module as standalone library (independently of raylib). +* Required types and functions are defined in the same module. +* +* #define SUPPORT_FILEFORMAT_WAV +* #define SUPPORT_FILEFORMAT_OGG +* #define SUPPORT_FILEFORMAT_XM +* #define SUPPORT_FILEFORMAT_MOD +* #define SUPPORT_FILEFORMAT_FLAC +* #define SUPPORT_FILEFORMAT_MP3 +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* DEPENDENCIES: +* miniaudio.h - Audio device management lib (https://github.com/dr-soft/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to miniaudio library +* +* Joshua Reisenauer (github: @kd7tck) (2015) +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if defined(RAUDIO_STANDALONE) + #include "raudio.h" + #include // Required for: va_list, va_start(), vfprintf(), va_end() +#else + #include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include "utils.h" // Required for: fopen() Android mapping +#endif + +#if defined(_WIN32) +// To avoid conflicting windows.h symbols with raylib, some flags are defined +// WARNING: Those flags avoid inclusion of some Win32 headers that could be required +// by user at some point and won't be included... +//------------------------------------------------------------------------------------- + +// If defined, the following flags inhibit definition of the indicated items. +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI defines and routines +#define NOKERNEL // All KERNEL defines and routines +#define NOUSER // All USER defines and routines +//#define NONLS // All NLS defines and routines +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff. +#define NOHELP // Help engine interface. +#define NOPROFILER // Profiler interface. +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions + +// Type required before windows.h inclusion +typedef struct tagMSG *LPMSG; + +#include + +// Type required by some unused function... +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER, *PBITMAPINFOHEADER; + +#include +#include +#include + +// Some required types defined for MSVC/TinyC compiler +#if defined(_MSC_VER) || defined(__TINYC__) + #include "propidl.h" +#endif +#endif + +#define MA_MALLOC RL_MALLOC +#define MA_FREE RL_FREE + +#define MA_NO_JACK +#define MA_NO_WAV +#define MA_NO_FLAC +#define MA_NO_MP3 +#define MINIAUDIO_IMPLEMENTATION +//#define MA_DEBUG_OUTPUT +#include "external/miniaudio.h" // miniaudio library +#undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro + +#include // Required for: malloc(), free() +#include // Required for: FILE, fopen(), fclose(), fread() + +#if defined(RAUDIO_STANDALONE) + #include // Required for: strcmp() [Used in IsFileExtension()] + + #if !defined(TRACELOG) + #define TRACELOG(level, ...) (void)0 + #endif + + // Allow custom memory allocators + #ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) + #endif + #ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) + #endif + #ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) + #endif + #ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) + #endif +#endif + +#if defined(SUPPORT_FILEFORMAT_OGG) + // TODO: Remap malloc()/free() calls to RL_MALLOC/RL_FREE + + #define STB_VORBIS_IMPLEMENTATION + #include "external/stb_vorbis.h" // OGG loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_XM) + #define JARXM_MALLOC RL_MALLOC + #define JARXM_FREE RL_FREE + + #define JAR_XM_IMPLEMENTATION + #include "external/jar_xm.h" // XM loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_MOD) + #define JARMOD_MALLOC RL_MALLOC + #define JARMOD_FREE RL_FREE + + #define JAR_MOD_IMPLEMENTATION + #include "external/jar_mod.h" // MOD loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_WAV) + #define DRWAV_MALLOC RL_MALLOC + #define DRWAV_REALLOC RL_REALLOC + #define DRWAV_FREE RL_FREE + + #define DR_WAV_IMPLEMENTATION + #include "external/dr_wav.h" // WAV loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_MP3) + #define DRMP3_MALLOC RL_MALLOC + #define DRMP3_REALLOC RL_REALLOC + #define DRMP3_FREE RL_FREE + + #define DR_MP3_IMPLEMENTATION + #include "external/dr_mp3.h" // MP3 loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_FLAC) + #define DRFLAC_MALLOC RL_MALLOC + #define DRFLAC_REALLOC RL_REALLOC + #define DRFLAC_FREE RL_FREE + + #define DR_FLAC_IMPLEMENTATION + #define DR_FLAC_NO_WIN32_IO + #include "external/dr_flac.h" // FLAC loading functions +#endif + +#if defined(_MSC_VER) + #undef bool +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef AUDIO_DEVICE_FORMAT + #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (float-32bit) +#endif +#ifndef AUDIO_DEVICE_CHANNELS + #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo +#endif + +#ifndef AUDIO_DEVICE_SAMPLE_RATE + #define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output channels: stereo +#endif +#ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS + #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels +#endif +#ifndef DEFAULT_AUDIO_BUFFER_SIZE + #define DEFAULT_AUDIO_BUFFER_SIZE 4096 // Default audio buffer size +#endif + + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Music context type +// NOTE: Depends on data structure provided by the library +// in charge of reading the different file types +typedef enum { + MUSIC_AUDIO_NONE = 0, + MUSIC_AUDIO_WAV, + MUSIC_AUDIO_OGG, + MUSIC_AUDIO_FLAC, + MUSIC_AUDIO_MP3, + MUSIC_MODULE_XM, + MUSIC_MODULE_MOD +} MusicContextType; + +#if defined(RAUDIO_STANDALONE) +typedef enum { + LOG_ALL, + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE +} TraceLogLevel; +#endif + +// NOTE: Different logic is used when feeding data to the playback device +// depending on whether or not data is streamed (Music vs Sound) +typedef enum { + AUDIO_BUFFER_USAGE_STATIC = 0, + AUDIO_BUFFER_USAGE_STREAM +} AudioBufferUsage; + +// Audio buffer structure +struct rAudioBuffer { + ma_data_converter converter; // Audio data converter + + float volume; // Audio buffer volume + float pitch; // Audio buffer pitch + + bool playing; // Audio buffer state: AUDIO_PLAYING + bool paused; // Audio buffer state: AUDIO_PAUSED + bool looping; // Audio buffer looping, always true for AudioStreams + int usage; // Audio buffer usage mode: STATIC or STREAM + + bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer) + unsigned int sizeInFrames; // Total buffer size in frames + unsigned int frameCursorPos; // Frame cursor position + unsigned int totalFramesProcessed; // Total frames processed in this buffer (required for play timing) + + unsigned char *data; // Data buffer, on music stream keeps filling + + rAudioBuffer *next; // Next audio buffer on the list + rAudioBuffer *prev; // Previous audio buffer on the list +}; + +#define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision + +// Audio data context +typedef struct AudioData { + struct { + ma_context context; // miniaudio context data + ma_device device; // miniaudio device + ma_mutex lock; // miniaudio mutex lock + bool isReady; // Check if audio device is ready + } System; + struct { + AudioBuffer *first; // Pointer to first AudioBuffer in the list + AudioBuffer *last; // Pointer to last AudioBuffer in the list + int defaultSize; // Default audio buffer size for audio streams + } Buffer; + struct { + unsigned int poolCounter; // AudioBuffer pointers pool counter + AudioBuffer *pool[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // Multichannel AudioBuffer pointers pool + unsigned int channels[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // AudioBuffer pool channels + } MultiChannel; +} AudioData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static AudioData AUDIO = { // Global AUDIO context + + // NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number + // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a + // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough + // In case of music-stalls, just increase this number + .Buffer.defaultSize = 0 +}; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message); +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume); + +#if defined(SUPPORT_FILEFORMAT_WAV) +static Wave LoadWAV(const unsigned char *fileData, unsigned int fileSize); // Load WAV file +static int SaveWAV(Wave wave, const char *fileName); // Save wave data as WAV file +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) +static Wave LoadOGG(const unsigned char *fileData, unsigned int fileSize); // Load OGG file +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) +static Wave LoadFLAC(const unsigned char *fileData, unsigned int fileSize); // Load FLAC file +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) +static Wave LoadMP3(const unsigned char *fileData, unsigned int fileSize); // Load MP3 file +#endif + +#if defined(RAUDIO_STANDALONE) +static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension +static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) +static bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write) +static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated +#endif + +//---------------------------------------------------------------------------------- +// AudioBuffer management functions declaration +// NOTE: Those functions are not exposed by raylib... for the moment +//---------------------------------------------------------------------------------- +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage); +void UnloadAudioBuffer(AudioBuffer *buffer); + +bool IsAudioBufferPlaying(AudioBuffer *buffer); +void PlayAudioBuffer(AudioBuffer *buffer); +void StopAudioBuffer(AudioBuffer *buffer); +void PauseAudioBuffer(AudioBuffer *buffer); +void ResumeAudioBuffer(AudioBuffer *buffer); +void SetAudioBufferVolume(AudioBuffer *buffer, float volume); +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); +void TrackAudioBuffer(AudioBuffer *buffer); +void UntrackAudioBuffer(AudioBuffer *buffer); +int GetAudioStreamBufferSizeDefault(); + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Device initialization and Closing +//---------------------------------------------------------------------------------- +// Initialize audio device +void InitAudioDevice(void) +{ + // TODO: Load AUDIO context memory dynamically? + + // Init audio context + ma_context_config ctxConfig = ma_context_config_init(); + ctxConfig.logCallback = OnLog; + + ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context"); + return; + } + + // Init audio device + // NOTE: Using the default device. Format is floating point because it simplifies mixing. + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device. + config.playback.format = AUDIO_DEVICE_FORMAT; + config.playback.channels = AUDIO_DEVICE_CHANNELS; + config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.System.device. + config.capture.format = ma_format_s16; + config.capture.channels = 1; + config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE; + config.dataCallback = OnSendAudioDataToDevice; + config.pUserData = NULL; + + result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device"); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running + // while there's at least one sound being played. + result = ma_device_start(&AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Mixing happens on a seperate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may + // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. + if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Init dummy audio buffers pool for multichannel sound playing + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + // WARNING: An empty audioBuffer is created (data = 0) + // AudioBuffer data just points to loaded sound data + AUDIO.MultiChannel.pool[i] = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC); + } + + TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Backend: miniaudio / %s", ma_get_backend_name(AUDIO.System.context.backend)); + TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat)); + TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels); + TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate); + TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods); + + AUDIO.System.isReady = true; +} + +// Close the audio device for all contexts +void CloseAudioDevice(void) +{ + if (AUDIO.System.isReady) + { + // Unload dummy audio buffers pool + // WARNING: They can be pointing to already unloaded data + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + //UnloadAudioBuffer(AUDIO.MultiChannel.pool[i]); + if (AUDIO.MultiChannel.pool[i] != NULL) + { + ma_data_converter_uninit(&AUDIO.MultiChannel.pool[i]->converter); + UntrackAudioBuffer(AUDIO.MultiChannel.pool[i]); + //RL_FREE(buffer->data); // Already unloaded by UnloadSound() + RL_FREE(AUDIO.MultiChannel.pool[i]); + } + } + + ma_mutex_uninit(&AUDIO.System.lock); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + + AUDIO.System.isReady = false; + + TRACELOG(LOG_INFO, "AUDIO: Device closed successfully"); + } + else TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized"); +} + +// Check if device has been initialized successfully +bool IsAudioDeviceReady(void) +{ + return AUDIO.System.isReady; +} + +// Set master volume (listener) +void SetMasterVolume(float volume) +{ + ma_device_set_master_volume(&AUDIO.System.device, volume); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Buffer management +//---------------------------------------------------------------------------------- + +// Initialize a new audio buffer (filled with silence) +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer)); + + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer"); + return NULL; + } + + if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1); + + // Audio data runs through a format converter + ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate); + converterConfig.resampling.allowDynamicSampleRate = true; // Required for pitch shifting + + ma_result result = ma_data_converter_init(&converterConfig, &audioBuffer->converter); + + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline"); + RL_FREE(audioBuffer); + return NULL; + } + + // Init audio buffer values + audioBuffer->volume = 1.0f; + audioBuffer->pitch = 1.0f; + audioBuffer->playing = false; + audioBuffer->paused = false; + audioBuffer->looping = false; + audioBuffer->usage = usage; + audioBuffer->frameCursorPos = 0; + audioBuffer->sizeInFrames = sizeInFrames; + + // Buffers should be marked as processed by default so that a call to + // UpdateAudioStream() immediately after initialization works correctly + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; + + // Track audio buffer to linked list next position + TrackAudioBuffer(audioBuffer); + + return audioBuffer; +} + +// Delete an audio buffer +void UnloadAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + ma_data_converter_uninit(&buffer->converter); + UntrackAudioBuffer(buffer); + RL_FREE(buffer->data); + RL_FREE(buffer); + } +} + +// Check if an audio buffer is playing +bool IsAudioBufferPlaying(AudioBuffer *buffer) +{ + bool result = false; + + if (buffer != NULL) result = (buffer->playing && !buffer->paused); + + return result; +} + +// Play an audio buffer +// NOTE: Buffer is restarted to the start. +// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained. +void PlayAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + buffer->playing = true; + buffer->paused = false; + buffer->frameCursorPos = 0; + } +} + +// Stop an audio buffer +void StopAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + if (IsAudioBufferPlaying(buffer)) + { + buffer->playing = false; + buffer->paused = false; + buffer->frameCursorPos = 0; + buffer->totalFramesProcessed = 0; + buffer->isSubBufferProcessed[0] = true; + buffer->isSubBufferProcessed[1] = true; + } + } +} + +// Pause an audio buffer +void PauseAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) buffer->paused = true; +} + +// Resume an audio buffer +void ResumeAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) buffer->paused = false; +} + +// Set volume for an audio buffer +void SetAudioBufferVolume(AudioBuffer *buffer, float volume) +{ + if (buffer != NULL) buffer->volume = volume; +} + +// Set pitch for an audio buffer +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) +{ + if ((buffer != NULL) && (pitch > 0.0f)) + { + // Pitching is just an adjustment of the sample rate. + // Note that this changes the duration of the sound: + // - higher pitches will make the sound faster + // - lower pitches make it slower + ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.config.sampleRateOut/pitch); + ma_data_converter_set_rate(&buffer->converter, buffer->converter.config.sampleRateIn, outputSampleRate); + + buffer->pitch = pitch; + } +} + +// Track audio buffer to linked list next position +void TrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer; + else + { + AUDIO.Buffer.last->next = buffer; + buffer->prev = AUDIO.Buffer.last; + } + + AUDIO.Buffer.last = buffer; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Untrack audio buffer from linked list +void UntrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next; + else buffer->prev->next = buffer->next; + + if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev; + else buffer->next->prev = buffer->prev; + + buffer->prev = NULL; + buffer->next = NULL; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Sounds loading and playing (.WAV) +//---------------------------------------------------------------------------------- + +// Load wave data from file +Wave LoadWave(const char *fileName) +{ + Wave wave = { 0 }; + + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading wave from memory data + wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, fileSize); + + RL_FREE(fileData); + } + + return wave; +} + +// Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Wave wave = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (TextIsEqual(fileExtLower, ".wav")) wave = LoadWAV(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (TextIsEqual(fileExtLower, ".ogg")) wave = LoadOGG(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (TextIsEqual(fileExtLower, ".flac")) wave = LoadFLAC(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (TextIsEqual(fileExtLower, ".mp3")) wave = LoadMP3(fileData, dataSize); +#endif + else TRACELOG(LOG_WARNING, "WAVE: File format not supported"); + + return wave; +} + +// Load sound from file +// NOTE: The entire file is loaded to memory to be played (no-streaming) +Sound LoadSound(const char *fileName) +{ + Wave wave = LoadWave(fileName); + + Sound sound = LoadSoundFromWave(wave); + + UnloadWave(wave); // Sound is loaded, we can unload wave + + return sound; +} + +// Load sound from wave data +// NOTE: Wave data must be unallocated manually +Sound LoadSoundFromWave(Wave wave) +{ + Sound sound = { 0 }; + + if (wave.data != NULL) + { + // When using miniaudio we need to do our own mixing. + // To simplify this we need convert the format of each sound to be consistent with + // the format used to open the playback AUDIO.System.device. We can do this two ways: + // + // 1) Convert the whole sound in one go at load time (here). + // 2) Convert the audio data in chunks at mixing time. + // + // First option has been selected, format conversion is done on the loading stage. + // The downside is that it uses more memory if the original sound is u8 or s16. + ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_uint32 frameCountIn = wave.sampleCount/wave.channels; + + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion"); + + AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + return sound; // early return to avoid dereferencing the audioBuffer null pointer + } + + frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion"); + + sound.sampleCount = frameCount*AUDIO_DEVICE_CHANNELS; + sound.stream.sampleRate = AUDIO.System.device.sampleRate; + sound.stream.sampleSize = 32; + sound.stream.channels = AUDIO_DEVICE_CHANNELS; + sound.stream.buffer = audioBuffer; + } + + return sound; +} + +// Unload wave data +void UnloadWave(Wave wave) +{ + if (wave.data != NULL) RL_FREE(wave.data); + + TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM"); +} + +// Unload sound +void UnloadSound(Sound sound) +{ + UnloadAudioBuffer(sound.stream.buffer); + + TRACELOG(LOG_INFO, "WAVE: Unloaded sound data from RAM"); +} + +// Update sound buffer with new data +void UpdateSound(Sound sound, const void *data, int samplesCount) +{ + if (sound.stream.buffer != NULL) + { + StopAudioBuffer(sound.stream.buffer); + + // TODO: May want to lock/unlock this since this data buffer is read at mixing time + memcpy(sound.stream.buffer->data, data, samplesCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.config.formatIn, sound.stream.buffer->converter.config.channelsIn)); + } +} + +// Export wave data to file +bool ExportWave(Wave wave, const char *fileName) +{ + bool success = false; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName); +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw sample data (without header) + // NOTE: It's up to the user to track wave parameters + success = SaveFileData(fileName, wave.data, wave.sampleCount*wave.sampleSize/8); + } + + if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName); + + return success; +} + +// Export wave sample data to code (.h) +bool ExportWaveAsCode(Wave wave, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + int waveDataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; + + // NOTE: Text data buffer size is estimated considering wave data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(6*waveDataSize + 2000, sizeof(char)); + + int bytesCount = 0; + bytesCount += sprintf(txtData + bytesCount, "\n//////////////////////////////////////////////////////////////////////////////////\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + bytesCount += sprintf(txtData + bytesCount, "// feedback and support: ray[at]raylib.com //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "//////////////////////////////////////////////////////////////////////////////////\n\n"); + + char varFileName[256] = { 0 }; +#if !defined(RAUDIO_STANDALONE) + // Get file name from path and convert variable name to uppercase + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } +#else + strcpy(varFileName, fileName); +#endif + + bytesCount += sprintf(txtData + bytesCount, "// Wave data information\n"); + bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_COUNT %u\n", varFileName, wave.sampleCount); + bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate); + bytesCount += sprintf(txtData + bytesCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize); + bytesCount += sprintf(txtData + bytesCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels); + + // Write byte data as hexadecimal text + bytesCount += sprintf(txtData + bytesCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize); + for (int i = 0; i < waveDataSize - 1; i++) bytesCount += sprintf(txtData + bytesCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); + bytesCount += sprintf(txtData + bytesCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); + + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + return success; +} + +// Play a sound +void PlaySound(Sound sound) +{ + PlayAudioBuffer(sound.stream.buffer); +} + +// Play a sound in the multichannel buffer pool +void PlaySoundMulti(Sound sound) +{ + int index = -1; + unsigned int oldAge = 0; + int oldIndex = -1; + + // find the first non playing pool entry + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + if (AUDIO.MultiChannel.channels[i] > oldAge) + { + oldAge = AUDIO.MultiChannel.channels[i]; + oldIndex = i; + } + + if (!IsAudioBufferPlaying(AUDIO.MultiChannel.pool[i])) + { + index = i; + break; + } + } + + // If no none playing pool members can be index choose the oldest + if (index == -1) + { + TRACELOG(LOG_WARNING, "SOUND: Buffer pool is already full, count: %i", AUDIO.MultiChannel.poolCounter); + + if (oldIndex == -1) + { + // Shouldn't be able to get here... but just in case something odd happens! + TRACELOG(LOG_WARNING, "SOUND: Buffer pool could not determine oldest buffer not playing sound"); + return; + } + + index = oldIndex; + + // Just in case... + StopAudioBuffer(AUDIO.MultiChannel.pool[index]); + } + + // Experimentally mutex lock doesn't seem to be needed this makes sense + // as pool[index] isn't playing and the only stuff we're copying + // shouldn't be changing... + + AUDIO.MultiChannel.channels[index] = AUDIO.MultiChannel.poolCounter; + AUDIO.MultiChannel.poolCounter++; + + AUDIO.MultiChannel.pool[index]->volume = sound.stream.buffer->volume; + AUDIO.MultiChannel.pool[index]->pitch = sound.stream.buffer->pitch; + AUDIO.MultiChannel.pool[index]->looping = sound.stream.buffer->looping; + AUDIO.MultiChannel.pool[index]->usage = sound.stream.buffer->usage; + AUDIO.MultiChannel.pool[index]->isSubBufferProcessed[0] = false; + AUDIO.MultiChannel.pool[index]->isSubBufferProcessed[1] = false; + AUDIO.MultiChannel.pool[index]->sizeInFrames = sound.stream.buffer->sizeInFrames; + AUDIO.MultiChannel.pool[index]->data = sound.stream.buffer->data; + + PlayAudioBuffer(AUDIO.MultiChannel.pool[index]); +} + +// Stop any sound played with PlaySoundMulti() +void StopSoundMulti(void) +{ + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) StopAudioBuffer(AUDIO.MultiChannel.pool[i]); +} + +// Get number of sounds playing in the multichannel buffer pool +int GetSoundsPlaying(void) +{ + int counter = 0; + + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + if (IsAudioBufferPlaying(AUDIO.MultiChannel.pool[i])) counter++; + } + + return counter; +} + +// Pause a sound +void PauseSound(Sound sound) +{ + PauseAudioBuffer(sound.stream.buffer); +} + +// Resume a paused sound +void ResumeSound(Sound sound) +{ + ResumeAudioBuffer(sound.stream.buffer); +} + +// Stop reproducing a sound +void StopSound(Sound sound) +{ + StopAudioBuffer(sound.stream.buffer); +} + +// Check if a sound is playing +bool IsSoundPlaying(Sound sound) +{ + return IsAudioBufferPlaying(sound.stream.buffer); +} + +// Set volume for a sound +void SetSoundVolume(Sound sound, float volume) +{ + SetAudioBufferVolume(sound.stream.buffer, volume); +} + +// Set pitch for a sound +void SetSoundPitch(Sound sound, float pitch) +{ + SetAudioBufferPitch(sound.stream.buffer, pitch); +} + +// Convert wave data to desired format +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) +{ + ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + ma_uint32 frameCountIn = wave->sampleCount/wave->channels; + + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); + if (frameCount == 0) + { + TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion"); + return; + } + + void *data = RL_MALLOC(frameCount*channels*(sampleSize/8)); + + frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate); + if (frameCount == 0) + { + TRACELOG(LOG_WARNING, "WAVE: Failed format conversion"); + return; + } + + wave->sampleCount = frameCount*channels; + wave->sampleSize = sampleSize; + wave->sampleRate = sampleRate; + wave->channels = channels; + RL_FREE(wave->data); + wave->data = data; +} + +// Copy a wave to a new wave +Wave WaveCopy(Wave wave) +{ + Wave newWave = { 0 }; + + newWave.data = RL_MALLOC(wave.sampleCount*wave.sampleSize/8); + + if (newWave.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newWave.data, wave.data, wave.sampleCount*wave.sampleSize/8); + + newWave.sampleCount = wave.sampleCount; + newWave.sampleRate = wave.sampleRate; + newWave.sampleSize = wave.sampleSize; + newWave.channels = wave.channels; + } + + return newWave; +} + +// Crop a wave to defined samples range +// NOTE: Security check in case of out-of-range +void WaveCrop(Wave *wave, int initSample, int finalSample) +{ + if ((initSample >= 0) && (initSample < finalSample) && + (finalSample > 0) && ((unsigned int)finalSample < wave->sampleCount)) + { + int sampleCount = finalSample - initSample; + + void *data = RL_MALLOC(sampleCount*wave->sampleSize/8); + + memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->sampleSize/8); + + RL_FREE(wave->data); + wave->data = data; + } + else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds"); +} + +// Load samples data from wave as a floats array +// NOTE 1: Returned sample values are normalized to range [-1..1] +// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples() +float *LoadWaveSamples(Wave wave) +{ + float *samples = (float *)RL_MALLOC(wave.sampleCount*sizeof(float)); + + // NOTE: sampleCount is the total number of interlaced samples (including channels) + + for (unsigned int i = 0; i < wave.sampleCount; i++) + { + if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f; + else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32767.0f; + else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i]; + } + + return samples; +} + +// Unload samples data loaded with LoadWaveSamples() +void UnloadWaveSamples(float *samples) +{ + RL_FREE(samples); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Music loading and stream playing (.OGG) +//---------------------------------------------------------------------------------- + +// Load music stream from file +Music LoadMusicStream(const char *fileName) +{ + Music music = { 0 }; + bool musicLoaded = false; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) + { + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + bool success = drwav_init_file(ctxWav, fileName, NULL); + + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + + if (success) + { + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = InitAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.sampleCount = (unsigned int)ctxWav->totalPCMFrameCount*ctxWav->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (IsFileExtension(fileName, ".ogg")) + { + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + + if (music.ctxData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = InitAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (IsFileExtension(fileName, ".flac")) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_file(fileName, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = InitAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (IsFileExtension(fileName, ".mp3")) + { + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + int result = drmp3_init_file(ctxMp3, fileName, NULL); + + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + + if (result > 0) + { + music.stream = InitAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (IsFileExtension(fileName, ".xm")) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName); + + music.ctxType = MUSIC_MODULE_XM; + music.ctxData = ctxXm; + + if (result == 0) // XM AUDIO.System.context created successfully + { + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) + bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) + bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS); + music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (IsFileExtension(fileName, ".mod")) + { + jar_mod_context_t *ctxMod = RL_CALLOC(1, sizeof(jar_mod_context_t)); + jar_mod_init(ctxMod); + int result = jar_mod_load_file(ctxMod, fileName); + + music.ctxType = MUSIC_MODULE_MOD; + music.ctxData = ctxMod; + + if (result > 0) + { + // NOTE: Only stereo is supported for MOD + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS); + music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: [%s] Fileformat not supported", fileName); + + if (!musicLoaded) + { + if (false) { } + #if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } + #endif + + music.ctxData = NULL; + TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: [%s] Music file successfully loaded:", fileName); + TRACELOG(LOG_INFO, " > Total samples: %i", music.sampleCount); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + } + + return music; +} + +// extension including period ".mod" +Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize) +{ + Music music = { 0 }; + bool musicLoaded = false; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (TextIsEqual(fileExtLower, ".wav")) + { + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + + bool success = drwav_init_memory(ctxWav, (const void*)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + + if (success) + { + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = InitAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.sampleCount = (unsigned int)ctxWav->totalPCMFrameCount*ctxWav->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (TextIsEqual(fileExtLower, ".flac")) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = InitAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (TextIsEqual(fileExtLower, ".mp3")) + { + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + + if (success) + { + music.stream = InitAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (TextIsEqual(fileExtLower, ".ogg")) + { + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + //music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + music.ctxData = stb_vorbis_open_memory((const unsigned char*)data, dataSize, NULL, NULL); + + if (music.ctxData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = InitAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (TextIsEqual(fileExtLower, ".xm")) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_safe(&ctxXm, (const char*)data, dataSize, AUDIO.System.device.sampleRate); + if (result == 0) // XM AUDIO.System.context created successfully + { + music.ctxType = MUSIC_MODULE_XM; + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) + bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) + bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, bits, 2); + music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + + music.ctxData = ctxXm; + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (TextIsEqual(fileExtLower, ".mod")) + { + jar_mod_context_t *ctxMod = RL_MALLOC(sizeof(jar_mod_context_t)); + int result = 0; + + jar_mod_init(ctxMod); + + // copy data to allocated memory for default UnloadMusicStream + unsigned char *newData = RL_MALLOC(dataSize); + int it = dataSize/sizeof(unsigned char); + for (int i = 0; i < it; i++){ + newData[i] = data[i]; + } + + // Memory loaded version for jar_mod_load_file() + if (dataSize && dataSize < 32*1024*1024) + { + ctxMod->modfilesize = dataSize; + ctxMod->modfile = newData; + if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize; + } + + if (result > 0) + { + music.ctxType = MUSIC_MODULE_MOD; + + // NOTE: Only stereo is supported for MOD + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, 16, 2); + music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels + music.looping = true; // Looping enabled by default + musicLoaded = true; + + music.ctxData = ctxMod; + musicLoaded = true; + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: [%s] Fileformat not supported", fileType); + + if (!musicLoaded) + { + if (false) { } + #if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } + #endif + + music.ctxData = NULL; + TRACELOG(LOG_WARNING, "FILEIO: [%s] Music memory could not be opened", fileType); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: [%s] Music memory successfully loaded:", fileType); + TRACELOG(LOG_INFO, " > Total samples: %i", music.sampleCount); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + } + + return music; +} + +// Unload music stream +void UnloadMusicStream(Music music) +{ + CloseAudioStream(music.stream); + + if (music.ctxData != NULL) + { + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } +#endif + } +} + +// Start music playing (open stream) +void PlayMusicStream(Music music) +{ + if (music.stream.buffer != NULL) + { + // For music streams, we need to make sure we maintain the frame cursor position + // This is a hack for this section of code in UpdateMusicStream() + // NOTE: In case window is minimized, music stream is stopped, just make sure to + // play again on window restore: if (IsMusicPlaying(music)) PlayMusicStream(music); + ma_uint32 frameCursorPos = music.stream.buffer->frameCursorPos; + PlayAudioStream(music.stream); // WARNING: This resets the cursor position. + music.stream.buffer->frameCursorPos = frameCursorPos; + } +} + +// Pause music playing +void PauseMusicStream(Music music) +{ + PauseAudioStream(music.stream); +} + +// Resume music playing +void ResumeMusicStream(Music music) +{ + ResumeAudioStream(music.stream); +} + +// Stop music playing (close stream) +void StopMusicStream(Music music) +{ + StopAudioStream(music.stream); + + switch (music.ctxType) + { +#if defined(SUPPORT_FILEFORMAT_WAV) + case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, 0); break; +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, 0); break; +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, 0); break; +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break; +#endif + default: break; + } +} + +// Update (re-fill) music buffers if data already processed +void UpdateMusicStream(Music music) +{ + if (music.stream.buffer == NULL) + return; + + if (music.ctxType == MUSIC_MODULE_XM) + jar_xm_set_max_loop_count(music.ctxData, music.looping ? 0 : 1); + + bool streamEnding = false; + + unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; + + // NOTE: Using dynamic allocation because it could require more than 16KB + void *pcm = RL_CALLOC(subBufferSizeInFrames*music.stream.channels*music.stream.sampleSize/8, 1); + + int samplesCount = 0; // Total size of data streamed in L+R samples for xm floats, individual L or R for ogg shorts + + // TODO: Get the sampleLeft using totalFramesProcessed... but first, get total frames processed correctly... + //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; + int sampleLeft = music.sampleCount - (music.stream.buffer->totalFramesProcessed*music.stream.channels); + + if (music.ctxType == MUSIC_MODULE_XM && music.looping) sampleLeft = subBufferSizeInFrames*4; + + while (IsAudioStreamProcessed(music.stream)) + { + if ((sampleLeft/music.stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music.stream.channels; + else samplesCount = sampleLeft; + + switch (music.ctxType) + { + #if defined(SUPPORT_FILEFORMAT_WAV) + case MUSIC_AUDIO_WAV: + { + // NOTE: Returns the number of samples to process (not required) + if (music.stream.sampleSize == 16) drwav_read_pcm_frames_s16((drwav *)music.ctxData, samplesCount/music.stream.channels, (short *)pcm); + else if (music.stream.sampleSize == 32) drwav_read_pcm_frames_f32((drwav *)music.ctxData, samplesCount/music.stream.channels, (float *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: + { + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)pcm, samplesCount); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: + { + // NOTE: Returns the number of samples to process (not required) + drflac_read_pcm_frames_s16((drflac *)music.ctxData, samplesCount, (short *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: + { + // NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed + drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, samplesCount/music.stream.channels, (float *)pcm); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: + { + switch (AUDIO_DEVICE_FORMAT) + { + case ma_format_f32: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples((jar_xm_context_t*)music.ctxData, (float*)pcm, samplesCount / 2); + break; + + case ma_format_s16: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples_16bit((jar_xm_context_t*)music.ctxData, (short*)pcm, samplesCount / 2); + break; + + case ma_format_u8: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples_8bit((jar_xm_context_t*)music.ctxData, (char*)pcm, samplesCount / 2); + break; + } + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: + { + // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 + jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)pcm, samplesCount/2, 0); + } break; + #endif + default: break; + } + + UpdateAudioStream(music.stream, pcm, samplesCount); + + if ((music.ctxType == MUSIC_MODULE_XM) || music.ctxType == MUSIC_MODULE_MOD) + { + if (samplesCount > 1) sampleLeft -= samplesCount/2; + else sampleLeft -= samplesCount; + } + else sampleLeft -= samplesCount; + + if (sampleLeft <= 0) + { + streamEnding = true; + break; + } + } + + // Free allocated pcm data + RL_FREE(pcm); + + // Reset audio stream for looping + if (streamEnding) + { + StopMusicStream(music); // Stop music (and reset) + if (music.looping) PlayMusicStream(music); // Play again + } + else + { + // NOTE: In case window is minimized, music stream is stopped, + // just make sure to play again on window restore + if (IsMusicPlaying(music)) PlayMusicStream(music); + } +} + +// Check if any music is playing +bool IsMusicPlaying(Music music) +{ + return IsAudioStreamPlaying(music.stream); +} + +// Set volume for music +void SetMusicVolume(Music music, float volume) +{ + SetAudioStreamVolume(music.stream, volume); +} + +// Set pitch for music +void SetMusicPitch(Music music, float pitch) +{ + SetAudioBufferPitch(music.stream.buffer, pitch); +} + +// Get music time length (in seconds) +float GetMusicTimeLength(Music music) +{ + float totalSeconds = 0.0f; + + totalSeconds = (float)music.sampleCount/(music.stream.sampleRate*music.stream.channels); + + return totalSeconds; +} + +// Get current music time played (in seconds) +float GetMusicTimePlayed(Music music) +{ +#if defined(SUPPORT_FILEFORMAT_XM) + if (music.ctxType == MUSIC_MODULE_XM) + { + uint64_t samples = 0; + jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &samples); + samples = samples % (music.sampleCount); + + return (float)(samples)/(music.stream.sampleRate*music.stream.channels); + } +#endif + float secondsPlayed = 0.0f; + if (music.stream.buffer != NULL) + { + //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; + unsigned int samplesPlayed = music.stream.buffer->totalFramesProcessed*music.stream.channels; + secondsPlayed = (float)samplesPlayed/(music.stream.sampleRate*music.stream.channels); + } + + return secondsPlayed; +} + +// Init audio stream (to stream audio pcm data) +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) +{ + AudioStream stream = { 0 }; + + stream.sampleRate = sampleRate; + stream.sampleSize = sampleSize; + stream.channels = channels; + + ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + // The size of a streaming buffer must be at least double the size of a period + unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; + unsigned int subBufferSize = GetAudioStreamBufferSizeDefault(); + + if (subBufferSize < periodSize) subBufferSize = periodSize; + + // Create a double audio buffer of defined size + stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); + + if (stream.buffer != NULL) + { + stream.buffer->looping = true; // Always loop for streaming buffers + TRACELOG(LOG_INFO, "STREAM: Initialized successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "STREAM: Failed to load audio buffer, stream could not be created"); + + return stream; +} + +// Close audio stream and free memory +void CloseAudioStream(AudioStream stream) +{ + UnloadAudioBuffer(stream.buffer); + + TRACELOG(LOG_INFO, "STREAM: Unloaded audio stream data from RAM"); +} + +// Update audio stream buffers with data +// NOTE 1: Only updates one buffer of the stream source: unqueue -> update -> queue +// NOTE 2: To unqueue a buffer it needs to be processed: IsAudioStreamProcessed() +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) +{ + if (stream.buffer != NULL) + { + if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]) + { + ma_uint32 subBufferToUpdate = 0; + + if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1]) + { + // Both buffers are available for updating. + // Update the first one and make sure the cursor is moved back to the front. + subBufferToUpdate = 0; + stream.buffer->frameCursorPos = 0; + } + else + { + // Just update whichever sub-buffer is processed. + subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1; + } + + ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2; + unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); + + // TODO: Get total frames processed on this buffer... DOES NOT WORK. + stream.buffer->totalFramesProcessed += subBufferSizeInFrames; + + // Does this API expect a whole buffer to be updated in one go? + // Assuming so, but if not will need to change this logic. + if (subBufferSizeInFrames >= (ma_uint32)samplesCount/stream.channels) + { + ma_uint32 framesToWrite = subBufferSizeInFrames; + + if (framesToWrite > ((ma_uint32)samplesCount/stream.channels)) framesToWrite = (ma_uint32)samplesCount/stream.channels; + + ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); + memcpy(subBuffer, data, bytesToWrite); + + // Any leftover frames should be filled with zeros. + ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; + + if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); + + stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false; + } + else TRACELOG(LOG_WARNING, "STREAM: Attempting to write too many frames to buffer"); + } + else TRACELOG(LOG_WARNING, "STREAM: Buffer not available for updating"); + } +} + +// Check if any audio stream buffers requires refill +bool IsAudioStreamProcessed(AudioStream stream) +{ + if (stream.buffer == NULL) return false; + + return (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]); +} + +// Play audio stream +void PlayAudioStream(AudioStream stream) +{ + PlayAudioBuffer(stream.buffer); +} + +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + PauseAudioBuffer(stream.buffer); +} + +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ResumeAudioBuffer(stream.buffer); +} + +// Check if audio stream is playing. +bool IsAudioStreamPlaying(AudioStream stream) +{ + return IsAudioBufferPlaying(stream.buffer); +} + +// Stop audio stream +void StopAudioStream(AudioStream stream) +{ + StopAudioBuffer(stream.buffer); +} + +// Set volume for audio stream (1.0 is max level) +void SetAudioStreamVolume(AudioStream stream, float volume) +{ + SetAudioBufferVolume(stream.buffer, volume); +} + +// Set pitch for audio stream (1.0 is base level) +void SetAudioStreamPitch(AudioStream stream, float pitch) +{ + SetAudioBufferPitch(stream.buffer, pitch); +} + +// Default size for new audio streams +void SetAudioStreamBufferSizeDefault(int size) +{ + AUDIO.Buffer.defaultSize = size; +} + +int GetAudioStreamBufferSizeDefault() +{ + // if the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate + if (AUDIO.Buffer.defaultSize == 0) + AUDIO.Buffer.defaultSize = AUDIO.System.device.sampleRate/30; + + return AUDIO.Buffer.defaultSize; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Log callback function +static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message) +{ + (void)pContext; + (void)pDevice; + + TRACELOG(LOG_WARNING, "miniaudio: %s", message); // All log messages from miniaudio are errors +} + +// Reads audio data from an AudioBuffer object in internal format. +static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount) +{ + ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames; + ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; + + if (currentSubBufferIndex > 1) return 0; + + // Another thread can update the processed state of buffers so + // we just take a copy here to try and avoid potential synchronization problems + bool isSubBufferProcessed[2]; + isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; + isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; + + ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.config.formatIn, audioBuffer->converter.config.channelsIn); + + // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0 + ma_uint32 framesRead = 0; + while (1) + { + // We break from this loop differently depending on the buffer's usage + // - For static buffers, we simply fill as much data as we can + // - For streaming buffers we only fill the halves of the buffer that are processed + // Unprocessed halves must keep their audio data in-tact + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + if (framesRead >= frameCount) break; + } + else + { + if (isSubBufferProcessed[currentSubBufferIndex]) break; + } + + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining == 0) break; + + ma_uint32 framesRemainingInOutputBuffer; + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + framesRemainingInOutputBuffer = audioBuffer->sizeInFrames - audioBuffer->frameCursorPos; + } + else + { + ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex; + framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); + } + + ma_uint32 framesToRead = totalFramesRemaining; + if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; + + memcpy((unsigned char *)framesOut + (framesRead*frameSizeInBytes), audioBuffer->data + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); + audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames; + framesRead += framesToRead; + + // If we've read to the end of the buffer, mark it as processed + if (framesToRead == framesRemainingInOutputBuffer) + { + audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; + isSubBufferProcessed[currentSubBufferIndex] = true; + + currentSubBufferIndex = (currentSubBufferIndex + 1)%2; + + // We need to break from this loop if we're not looping + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + } + } + + // Zero-fill excess + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining > 0) + { + memset((unsigned char *)framesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + + // For static buffers we can fill the remaining frames with silence for safety, but we don't want + // to report those frames as "read". The reason for this is that the caller uses the return value + // to know whether or not a non-looping sound has finished playback. + if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining; + } + + return framesRead; +} + +// Reads audio data from an AudioBuffer object in device format. Returned data will be in a format appropriate for mixing. +static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount) +{ + // What's going on here is that we're continuously converting data from the AudioBuffer's internal format to the mixing format, which + // should be defined by the output format of the data converter. We do this until frameCount frames have been output. The important + // detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output + // frames. This can be achieved with ma_data_converter_get_required_input_frame_count(). + ma_uint8 inputBuffer[4096]; + ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.config.formatIn, audioBuffer->converter.config.channelsIn); + + ma_uint32 totalOutputFramesProcessed = 0; + while (totalOutputFramesProcessed < frameCount) + { + ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed; + + ma_uint64 inputFramesToProcessThisIteration = ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration); + if (inputFramesToProcessThisIteration > inputBufferFrameCap) + { + inputFramesToProcessThisIteration = inputBufferFrameCap; + } + + float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.config.channelsOut); + + /* At this point we can convert the data to our mixing format. */ + ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration); /* Safe cast. */ + ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration; + ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration); + + totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; /* Safe cast. */ + + if (inputFramesProcessedThisIteration < inputFramesToProcessThisIteration) + { + break; /* Ran out of input data. */ + } + + /* This should never be hit, but will add it here for safety. Ensures we get out of the loop when no input nor output frames are processed. */ + if (inputFramesProcessedThisIteration == 0 && outputFramesProcessedThisIteration == 0) + { + break; + } + } + + return totalOutputFramesProcessed; +} + + +// Sending audio data to device callback function +// NOTE: All the mixing takes place here +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount) +{ + (void)pDevice; + + // Mixing is basically just an accumulation, we need to initialize the output buffer to 0 + memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format)); + + // Using a mutex here for thread-safety which makes things not real-time + // This is unlikely to be necessary for this project, but may want to consider how you might want to avoid this + ma_mutex_lock(&AUDIO.System.lock); + { + for (AudioBuffer *audioBuffer = AUDIO.Buffer.first; audioBuffer != NULL; audioBuffer = audioBuffer->next) + { + // Ignore stopped or paused sounds + if (!audioBuffer->playing || audioBuffer->paused) continue; + + ma_uint32 framesRead = 0; + + while (1) + { + if (framesRead >= frameCount) break; + + // Just read as much data as we can from the stream + ma_uint32 framesToRead = (frameCount - framesRead); + + while (framesToRead > 0) + { + float tempBuffer[1024]; // 512 frames for stereo + + ma_uint32 framesToReadRightNow = framesToRead; + if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS) + { + framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS; + } + + ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat(audioBuffer, tempBuffer, framesToReadRightNow); + if (framesJustRead > 0) + { + float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels); + float *framesIn = tempBuffer; + + MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer->volume); + + framesToRead -= framesJustRead; + framesRead += framesJustRead; + } + + if (!audioBuffer->playing) + { + framesRead = frameCount; + break; + } + + // If we weren't able to read all the frames we requested, break + if (framesJustRead < framesToReadRightNow) + { + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + else + { + // Should never get here, but just for safety, + // move the cursor position back to the start and continue the loop + audioBuffer->frameCursorPos = 0; + continue; + } + } + } + + // If for some reason we weren't able to read every frame we'll need to break from the loop + // Not doing this could theoretically put us into an infinite loop + if (framesToRead > 0) break; + } + } + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// This is the main mixing function. Mixing is pretty simple in this project - it's just an accumulation. +// NOTE: framesOut is both an input and an output. It will be initially filled with zeros outside of this function. +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume) +{ + for (ma_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) + { + for (ma_uint32 iChannel = 0; iChannel < AUDIO.System.device.playback.channels; ++iChannel) + { + float *frameOut = framesOut + (iFrame*AUDIO.System.device.playback.channels); + const float *frameIn = framesIn + (iFrame*AUDIO.System.device.playback.channels); + + frameOut[iChannel] += (frameIn[iChannel]*localVolume); + } + } +} + +#if defined(SUPPORT_FILEFORMAT_WAV) +// Load WAV file data into Wave structure +// NOTE: Using dr_wav library +static Wave LoadWAV(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + drwav wav = { 0 }; + + bool success = drwav_init_memory(&wav, fileData, fileSize, NULL); + + if (success) + { + wave.sampleCount = (unsigned int)wav.totalPCMFrameCount*wav.channels; + wave.sampleRate = wav.sampleRate; + wave.sampleSize = 16; // NOTE: We are forcing conversion to 16bit + wave.channels = wav.channels; + wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); + drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, wave.data); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data"); + + drwav_uninit(&wav); + + return wave; +} + +// Save wave data as WAV file +// NOTE: Using dr_wav library +static int SaveWAV(Wave wave, const char *fileName) +{ + int success = false; + + drwav wav = { 0 }; + drwav_data_format format = { 0 }; + format.container = drwav_container_riff; + format.format = DR_WAVE_FORMAT_PCM; + format.channels = wave.channels; + format.sampleRate = wave.sampleRate; + format.bitsPerSample = wave.sampleSize; + + void *fileData = NULL; + size_t fileDataSize = 0; + success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); + if (success) success = (int)drwav_write_pcm_frames(&wav, wave.sampleCount/wave.channels, wave.data); + drwav_result result = drwav_uninit(&wav); + + if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize); + + drwav_free(fileData, NULL); + + return success; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_OGG) +// Load OGG file data into Wave structure +// NOTE: Using stb_vorbis library +static Wave LoadOGG(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + + stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, fileSize, NULL, NULL); + + if (oggData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info(oggData); + + wave.sampleRate = info.sample_rate; + wave.sampleSize = 16; // 16 bit per sample (short) + wave.channels = info.channels; + wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData)*info.channels; // Independent by channel + + float totalSeconds = stb_vorbis_stream_length_in_seconds(oggData); + if (totalSeconds > 10) TRACELOG(LOG_WARNING, "WAVE: OGG audio length larger than 10 seconds (%f sec.), that's a big file in memory, consider music streaming", totalSeconds); + + wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); + + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.sampleCount); + TRACELOG(LOG_INFO, "WAVE: OGG data loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + + stb_vorbis_close(oggData); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); + + return wave; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_FLAC) +// Load FLAC file data into Wave structure +// NOTE: Using dr_flac library +static Wave LoadFLAC(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + + // Decode the entire FLAC file in one go + unsigned long long int totalFrameCount = 0; + wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, fileSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); + + if (wave.data != NULL) + { + wave.sampleCount = (unsigned int)totalFrameCount*wave.channels; + wave.sampleSize = 16; + + TRACELOG(LOG_INFO, "WAVE: FLAC data loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); + + return wave; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_MP3) +// Load MP3 file data into Wave structure +// NOTE: Using dr_mp3 library +static Wave LoadMP3(const unsigned char *fileData, unsigned int fileSize) +{ + Wave wave = { 0 }; + drmp3_config config = { 0 }; + + // Decode the entire MP3 file in one go + unsigned long long int totalFrameCount = 0; + wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, fileSize, &config, &totalFrameCount, NULL); + + if (wave.data != NULL) + { + wave.channels = config.channels; + wave.sampleRate = config.sampleRate; + wave.sampleCount = (int)totalFrameCount*wave.channels; + wave.sampleSize = 32; + + // NOTE: Only support up to 2 channels (mono, stereo) + // TODO: Really? + if (wave.channels > 2) TRACELOG(LOG_WARNING, "WAVE: MP3 channels number (%i) not supported", wave.channels); + + TRACELOG(LOG_INFO, "WAVE: MP3 file loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); + + return wave; +} +#endif + +// Some required functions for audio standalone module version +#if defined(RAUDIO_STANDALONE) +// Check file extension +static bool IsFileExtension(const char *fileName, const char *ext) +{ + bool result = false; + const char *fileExt; + + if ((fileExt = strrchr(fileName, '.')) != NULL) + { + if (strcmp(fileExt, ext) == 0) result = true; + } + + return result; +} + +// Load data from file into a buffer +static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) +{ + unsigned char *data = NULL; + *bytesRead = 0; + + if (fileName != NULL) + { + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); + + // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] + unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file); + *bytesRead = count; + + if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Save data to file from buffer +static bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) +{ + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), bytesToWrite, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != bytesToWrite) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); +} + +// Save text data to file (write), string must be '\0' terminated +static bool SaveFileText(const char *fileName, char *text) +{ + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = fprintf(file, "%s", text); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); +} +#endif + +#undef AudioBuffer diff --git a/raylib_pi4_test/raudio.h b/raylib_pi4_test/raudio.h new file mode 100644 index 0000000..cf8b767 --- /dev/null +++ b/raylib_pi4_test/raudio.h @@ -0,0 +1,198 @@ +/********************************************************************************************** +* +* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio +* +* FEATURES: +* - Manage audio device (init/close) +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* - Manage mixing channels +* - Manage raw audio context +* +* DEPENDENCIES: +* miniaudio.h - Audio device management lib (https://github.com/dr-soft/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to miniaudio library +* +* Joshua Reisenauer (github: @kd7tck) (2015) +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAUDIO_H +#define RAUDIO_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#ifndef __cplusplus +// Boolean type + #if !defined(_STDBOOL_H) + typedef enum { false, true } bool; + #define _STDBOOL_H + #endif +#endif + +// Wave type, defines audio wave data +typedef struct Wave { + unsigned int sampleCount; // Total number of samples + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + void *data; // Buffer data pointer +} Wave; + +typedef struct rAudioBuffer rAudioBuffer; + +// Audio stream type +// NOTE: Useful to create custom audio streams not bound to a specific file +typedef struct AudioStream { + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + + rAudioBuffer *buffer; // Pointer to internal data used by the audio system +} AudioStream; + +// Sound source type +typedef struct Sound { + unsigned int sampleCount; // Total number of samples + AudioStream stream; // Audio stream +} Sound; + +// Music stream type (audio file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct Music { + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type + + bool looping; // Music looping enable + unsigned int sampleCount; // Total number of samples + + AudioStream stream; // Audio stream +} Music; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +// Audio device management functions +void InitAudioDevice(void); // Initialize audio device and context +void CloseAudioDevice(void); // Close the audio device and context +bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +void SetMasterVolume(float volume); // Set master volume (listener) + +// Wave/Sound loading/unloading functions +Wave LoadWave(const char *fileName); // Load wave data from file +Sound LoadSound(const char *fileName); // Load sound from file +Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data +void UnloadWave(Wave wave); // Unload wave data +void UnloadSound(Sound sound); // Unload sound +void ExportWave(Wave wave, const char *fileName); // Export wave data to file +void ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h) + +// Wave/Sound management functions +void PlaySound(Sound sound); // Play a sound +void StopSound(Sound sound); // Stop playing a sound +void PauseSound(Sound sound); // Pause a sound +void ResumeSound(Sound sound); // Resume a paused sound +void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel +bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +Wave WaveCopy(Wave wave); // Copy a wave to a new wave +void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range +float *GetWaveData(Wave wave); // Get samples data from wave as a floats array + +// Music management functions +Music LoadMusicStream(const char *fileName); // Load music stream from file +Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize); // Load module music from data +void UnloadMusicStream(Music music); // Unload music stream +void PlayMusicStream(Music music); // Start music playing +void UpdateMusicStream(Music music); // Updates buffers for music streaming +void StopMusicStream(Music music); // Stop music playing +void PauseMusicStream(Music music); // Pause music playing +void ResumeMusicStream(Music music); // Resume playing paused music +bool IsMusicPlaying(Music music); // Check if music is playing +void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +float GetMusicTimeLength(Music music); // Get music time length (in seconds) +float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Init audio stream (to stream raw audio pcm data) +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data +void CloseAudioStream(AudioStream stream); // Close audio stream and free memory +bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +void PlayAudioStream(AudioStream stream); // Play audio stream +void PauseAudioStream(AudioStream stream); // Pause audio stream +void ResumeAudioStream(AudioStream stream); // Resume audio stream +bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +void StopAudioStream(AudioStream stream); // Stop audio stream +void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams + +#ifdef __cplusplus +} +#endif + +#endif // RAUDIO_H diff --git a/raylib_pi4_test/raylib.dll.rc b/raylib_pi4_test/raylib.dll.rc new file mode 100644 index 0000000..f4d90f4 --- /dev/null +++ b/raylib_pi4_test/raylib.dll.rc @@ -0,0 +1,27 @@ +GLFW_ICON ICON "raylib.ico" + +1 VERSIONINFO +FILEVERSION 3,7,0,0 +PRODUCTVERSION 3,7,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + //BLOCK "080904E4" // English UK + BLOCK "040904E4" // English US + BEGIN + //VALUE "CompanyName", "raylib technologies" + VALUE "FileDescription", "raylib dynamic library (www.raylib.com)" + VALUE "FileVersion", "3.7.0" + VALUE "InternalName", "raylib.dll" + VALUE "LegalCopyright", "(c) 2021 Ramon Santamaria (@raysan5)" + VALUE "OriginalFilename", "raylib.dll" + VALUE "ProductName", "raylib" + VALUE "ProductVersion", "3.7.0" + END + END + BLOCK "VarFileInfo" + BEGIN + //VALUE "Translation", 0x809, 1252 // English UK + VALUE "Translation", 0x409, 1252 // English US + END +END diff --git a/raylib_pi4_test/raylib.dll.rc.data b/raylib_pi4_test/raylib.dll.rc.data new file mode 100644 index 0000000..f4e5b3f Binary files /dev/null and b/raylib_pi4_test/raylib.dll.rc.data differ diff --git a/raylib_pi4_test/raylib.h b/raylib_pi4_test/raylib.h new file mode 100644 index 0000000..ead617c --- /dev/null +++ b/raylib_pi4_test/raylib.h @@ -0,0 +1,1520 @@ +/********************************************************************************************** +* +* raylib - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, UWP, Android, Raspberry Pi, HTML5. +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES2 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, XNA fonts, AngelCode fonts) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM, glTF) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* One default Texture2D is loaded on rlglInit() [rlgl] (OpenGL 3.3 or ES2) +* One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES (included): +* [core] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input (PLATFORM_DESKTOP) +* [rlgl] glad (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (PLATFORM_DESKTOP) +* [raudio] miniaudio (David Reid - github.com/dr-soft/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [core] msf_gif (Miles Fogle) for GIF recording +* [core] sinfl (Micha Mettke) for DEFLATE decompression algorythm +* [core] sdefl (Micha Mettke) for DEFLATE compression algorythm +* [textures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [textures] stb_image_write (Sean Barret) for image writting (BMP, TGA, PNG, JPG) +* [textures] stb_image_resize (Sean Barret) for image resizing algorithms +* [textures] stb_perlin (Sean Barret) for Perlin noise image generation +* [text] stb_truetype (Sean Barret) for ttf fonts loading +* [text] stb_rect_pack (Sean Barret) for rectangles packing +* [models] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [models] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [models] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include // Required for: va_list - Only used by TraceLogCallback + +#if defined(_WIN32) + // Microsoft attibutes to tell compiler that symbols are imported/exported from a .dll + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building raylib as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RLAPI // We are building or using raylib as a static library + #endif +#else + #define RLAPI // We are building or using raylib as a static library (or Linux shared library) +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#define DEG2RAD (PI/180.0f) +#define RAD2DEG (180.0f/PI) + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized from { } initializers. +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +// Temporal hacks to avoid breaking old codebases using +// deprecated raylib implementation or definitions +#define FormatText TextFormat +#define LoadText LoadFileText +#define GetExtension GetFileExtension +#define GetImageData LoadImageColors +#define FILTER_POINT TEXTURE_FILTER_POINT +#define FILTER_BILINEAR TEXTURE_FILTER_BILINEAR +#define MAP_DIFFUSE MATERIAL_MAP_DIFFUSE +#define UNCOMPRESSED_R8G8B8A8 PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + +//---------------------------------------------------------------------------------- +// Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum { false, true } bool; +#endif + +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; + +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; + +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; + +// Quaternion type, same as Vector4 +typedef Vector4 Quaternion; + +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; +} Matrix; + +// Color type, RGBA (32bit) +typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; +} Color; + +// Rectangle type +typedef struct Rectangle { + float x; + float y; + float width; + float height; +} Rectangle; + +// Image type, bpp always RGBA (32bit) +// NOTE: Data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture type +// NOTE: Data stored in GPU memory +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D type, same as Texture +typedef Texture Texture2D; + +// TextureCubemap type, actually, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture type, for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D type, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// N-Patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// Font character info +typedef struct CharInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} CharInfo; + +// Font type, includes texture and charSet array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int charsCount; // Number of characters + int charsPadding; // Padding around the chars + Texture2D texture; // Characters texture atlas + Rectangle *recs; // Characters rectangles in texture + CharInfo *chars; // Characters info data +} Font; + +#define SpriteFont Font // SpriteFont type fallback, defaults to Font + +// Camera type, defines a camera position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view apperture in Y (degrees) in perspective, used as near plane width in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D type, defines a 2d camera +typedef struct Camera2D { + Vector2 offset; // Camera offset (displacement from target) + Vector2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} Camera2D; + +// Vertex data definning a mesh +// NOTE: Data stored in CPU memory (and GPU) +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Default vertex data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex second texture coordinates (useful for lightmaps) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices;// Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + int *boneIds; // Vertex bone ids, up to 4 bones influence by vertex (skinning) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader type (generic) +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (MAX_SHADER_LOCATIONS) +} Shader; + +// Material texture map +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material type (generic) +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transformation properties +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone information +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Model type +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) +} Model; + +// Model animation +typedef struct ModelAnimation { + int boneCount; // Number of bones + int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) + Transform **framePoses; // Poses array by frame +} ModelAnimation; + +// Ray type (useful for raycast) +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction +} Ray; + +// Raycast hit information +typedef struct RayHitInfo { + bool hit; // Did the ray hit something? + float distance; // Distance to nearest hit + Vector3 position; // Position of nearest hit + Vector3 normal; // Surface normal of hit +} RayHitInfo; + +// Bounding box type +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave type, defines audio wave data +typedef struct Wave { + unsigned int sampleCount; // Total number of samples + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) + void *data; // Buffer data pointer +} Wave; + +typedef struct rAudioBuffer rAudioBuffer; + +// Audio stream type +// NOTE: Useful to create custom audio streams not bound to a specific file +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo) +} AudioStream; + +// Sound source type +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int sampleCount; // Total number of samples +} Sound; + +// Music stream type (audio file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int sampleCount; // Total number of samples + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float vScreenCenter; // Screen center in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VR Stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts +typedef enum { + KEY_NULL = 0, + // Alphanumeric keys + KEY_APOSTROPHE = 39, + KEY_COMMA = 44, + KEY_MINUS = 45, + KEY_PERIOD = 46, + KEY_SLASH = 47, + KEY_ZERO = 48, + KEY_ONE = 49, + KEY_TWO = 50, + KEY_THREE = 51, + KEY_FOUR = 52, + KEY_FIVE = 53, + KEY_SIX = 54, + KEY_SEVEN = 55, + KEY_EIGHT = 56, + KEY_NINE = 57, + KEY_SEMICOLON = 59, + KEY_EQUAL = 61, + KEY_A = 65, + KEY_B = 66, + KEY_C = 67, + KEY_D = 68, + KEY_E = 69, + KEY_F = 70, + KEY_G = 71, + KEY_H = 72, + KEY_I = 73, + KEY_J = 74, + KEY_K = 75, + KEY_L = 76, + KEY_M = 77, + KEY_N = 78, + KEY_O = 79, + KEY_P = 80, + KEY_Q = 81, + KEY_R = 82, + KEY_S = 83, + KEY_T = 84, + KEY_U = 85, + KEY_V = 86, + KEY_W = 87, + KEY_X = 88, + KEY_Y = 89, + KEY_Z = 90, + + // Function keys + KEY_SPACE = 32, + KEY_ESCAPE = 256, + KEY_ENTER = 257, + KEY_TAB = 258, + KEY_BACKSPACE = 259, + KEY_INSERT = 260, + KEY_DELETE = 261, + KEY_RIGHT = 262, + KEY_LEFT = 263, + KEY_DOWN = 264, + KEY_UP = 265, + KEY_PAGE_UP = 266, + KEY_PAGE_DOWN = 267, + KEY_HOME = 268, + KEY_END = 269, + KEY_CAPS_LOCK = 280, + KEY_SCROLL_LOCK = 281, + KEY_NUM_LOCK = 282, + KEY_PRINT_SCREEN = 283, + KEY_PAUSE = 284, + KEY_F1 = 290, + KEY_F2 = 291, + KEY_F3 = 292, + KEY_F4 = 293, + KEY_F5 = 294, + KEY_F6 = 295, + KEY_F7 = 296, + KEY_F8 = 297, + KEY_F9 = 298, + KEY_F10 = 299, + KEY_F11 = 300, + KEY_F12 = 301, + KEY_LEFT_SHIFT = 340, + KEY_LEFT_CONTROL = 341, + KEY_LEFT_ALT = 342, + KEY_LEFT_SUPER = 343, + KEY_RIGHT_SHIFT = 344, + KEY_RIGHT_CONTROL = 345, + KEY_RIGHT_ALT = 346, + KEY_RIGHT_SUPER = 347, + KEY_KB_MENU = 348, + KEY_LEFT_BRACKET = 91, + KEY_BACKSLASH = 92, + KEY_RIGHT_BRACKET = 93, + KEY_GRAVE = 96, + + // Keypad keys + KEY_KP_0 = 320, + KEY_KP_1 = 321, + KEY_KP_2 = 322, + KEY_KP_3 = 323, + KEY_KP_4 = 324, + KEY_KP_5 = 325, + KEY_KP_6 = 326, + KEY_KP_7 = 327, + KEY_KP_8 = 328, + KEY_KP_9 = 329, + KEY_KP_DECIMAL = 330, + KEY_KP_DIVIDE = 331, + KEY_KP_MULTIPLY = 332, + KEY_KP_SUBTRACT = 333, + KEY_KP_ADD = 334, + KEY_KP_ENTER = 335, + KEY_KP_EQUAL = 336, + // Android key buttons + KEY_BACK = 4, + KEY_MENU = 82, + KEY_VOLUME_UP = 24, + KEY_VOLUME_DOWN = 25 +} KeyboardKey; + +// Mouse buttons +typedef enum { + MOUSE_LEFT_BUTTON = 0, + MOUSE_RIGHT_BUTTON = 1, + MOUSE_MIDDLE_BUTTON = 2 +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, + MOUSE_CURSOR_ARROW = 1, + MOUSE_CURSOR_IBEAM = 2, + MOUSE_CURSOR_CROSSHAIR = 3, + MOUSE_CURSOR_POINTING_HAND = 4, + MOUSE_CURSOR_RESIZE_EW = 5, // The horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // The vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // The top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omni-directional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + // This is here just for error checking + GAMEPAD_BUTTON_UNKNOWN = 0, + + // This is normally a DPAD + GAMEPAD_BUTTON_LEFT_FACE_UP, + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, + GAMEPAD_BUTTON_LEFT_FACE_DOWN, + GAMEPAD_BUTTON_LEFT_FACE_LEFT, + + // This normally corresponds with PlayStation and Xbox controllers + // XBOX: [Y,X,A,B] + // PS3: [Triangle,Square,Cross,Circle] + // No support for 6 button controllers though.. + GAMEPAD_BUTTON_RIGHT_FACE_UP, + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, + + // Triggers + GAMEPAD_BUTTON_LEFT_TRIGGER_1, + GAMEPAD_BUTTON_LEFT_TRIGGER_2, + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, + + // These are buttons in the center of the gamepad + GAMEPAD_BUTTON_MIDDLE_LEFT, // PS3 Select + GAMEPAD_BUTTON_MIDDLE, // PS Button/XBOX Button + GAMEPAD_BUTTON_MIDDLE_RIGHT, // PS3 Start + + // These are the joystick press in buttons + GAMEPAD_BUTTON_LEFT_THUMB, + GAMEPAD_BUTTON_RIGHT_THUMB +} GamepadButton; + +// Gamepad axis +typedef enum { + // Left stick + GAMEPAD_AXIS_LEFT_X = 0, + GAMEPAD_AXIS_LEFT_Y = 1, + + // Right stick + GAMEPAD_AXIS_RIGHT_X = 2, + GAMEPAD_AXIS_RIGHT_Y = 3, + + // Pressure levels for the back triggers + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // [1..-1] (pressure-level) + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // [1..-1] (pressure-level) +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // MATERIAL_MAP_DIFFUSE + MATERIAL_MAP_METALNESS = 1, // MATERIAL_MAP_SPECULAR + MATERIAL_MAP_NORMAL = 2, + MATERIAL_MAP_ROUGHNESS = 3, + MATERIAL_MAP_OCCLUSION, + MATERIAL_MAP_EMISSION, + MATERIAL_MAP_HEIGHT, + MATERIAL_MAP_BRDG, + MATERIAL_MAP_CUBEMAP, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MATERIAL_MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MATERIAL_MAP_PREFILTER // NOTE: Uses GL_TEXTURE_CUBE_MAP +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, + SHADER_LOC_VERTEX_TEXCOORD01, + SHADER_LOC_VERTEX_TEXCOORD02, + SHADER_LOC_VERTEX_NORMAL, + SHADER_LOC_VERTEX_TANGENT, + SHADER_LOC_VERTEX_COLOR, + SHADER_LOC_MATRIX_MVP, + SHADER_LOC_MATRIX_VIEW, + SHADER_LOC_MATRIX_PROJECTION, + SHADER_LOC_MATRIX_MODEL, + SHADER_LOC_MATRIX_NORMAL, + SHADER_LOC_VECTOR_VIEW, + SHADER_LOC_COLOR_DIFFUSE, + SHADER_LOC_COLOR_SPECULAR, + SHADER_LOC_COLOR_AMBIENT, + SHADER_LOC_MAP_ALBEDO, // SHADER_LOC_MAP_DIFFUSE + SHADER_LOC_MAP_METALNESS, // SHADER_LOC_MAP_SPECULAR + SHADER_LOC_MAP_NORMAL, + SHADER_LOC_MAP_ROUGHNESS, + SHADER_LOC_MAP_OCCLUSION, + SHADER_LOC_MAP_EMISSION, + SHADER_LOC_MAP_HEIGHT, + SHADER_LOC_MAP_CUBEMAP, + SHADER_LOC_MAP_IRRADIANCE, + SHADER_LOC_MAP_PREFILTER, + SHADER_LOC_MAP_BRDF +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, + SHADER_UNIFORM_VEC2, + SHADER_UNIFORM_VEC3, + SHADER_UNIFORM_VEC4, + SHADER_UNIFORM_INT, + SHADER_UNIFORM_IVEC2, + SHADER_UNIFORM_IVEC3, + SHADER_UNIFORM_IVEC4, + SHADER_UNIFORM_SAMPLER2D +} ShaderUniformDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_RGB565_BE, // 16 bpp (big endian) + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel aproximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by an horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE, // Layout is defined by a 4x3 cross with cubemap faces + CUBEMAP_LAYOUT_PANORAMA // Layout is defined by a panorama image (equirectangular map) +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_CUSTOM // Belnd textures using custom src/dst factors (use rlSetBlendMode()) +} BlendMode; + +// Gestures +// NOTE: It could be used as flags to enable only some gestures +typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP = 1, + GESTURE_DOUBLETAP = 2, + GESTURE_HOLD = 4, + GESTURE_DRAG = 8, + GESTURE_SWIPE_RIGHT = 16, + GESTURE_SWIPE_LEFT = 32, + GESTURE_SWIPE_UP = 64, + GESTURE_SWIPE_DOWN = 128, + GESTURE_PINCH_IN = 256, + GESTURE_PINCH_OUT = 512 +} Gestures; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, + CAMERA_FREE, + CAMERA_ORBITAL, + CAMERA_FIRST_PERSON, + CAMERA_THIRD_PERSON +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, + CAMERA_ORTHOGRAPHIC +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: This callbacks are intended for advance users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char* (*LoadFileDataCallback)(const char* fileName, unsigned int* bytesRead); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, unsigned int bytesToWrite); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char* fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data + + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP) +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) +RLAPI void SetWindowIcon(Image image); // Set icon for window (only PLATFORM_DESKTOP) +RLAPI void SetWindowTitle(const char *title); // Set title for window (only PLATFORM_DESKTOP) +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen (only PLATFORM_DESKTOP) +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window (fullscreen mode) +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current connected monitor +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (max available by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (max available by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the primary monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content + +// Cursor-related functions +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disables cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the current screen. + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) +RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing +RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Initialize 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // Ends 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Initializes 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Initializes render texture for drawing +RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Returns a ray trace from mouse position +RLAPI Matrix GetCameraMatrix(Camera camera); // Returns camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Returns camera 2d transform matrix +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Returns the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Returns size position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Returns the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Returns the world space position for a 2d camera screen space position + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI int GetFPS(void); // Returns current FPS +RLAPI float GetFrameTime(void); // Returns time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Returns elapsed time in seconds since InitWindow() + +// Misc. functions +RLAPI int GetRandomValue(int min, int max); // Returns a random value between min and max (both included) +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) + +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advance users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +// Files management functions +RLAPI unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(unsigned char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext);// Check file extension (including point: .png, .wav) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: ".png") +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI char **GetDirectoryFiles(const char *dirPath, int *count); // Get filenames in a directory path (memory should be freed) +RLAPI void ClearDirectoryFiles(void); // Clear directory files paths buffers (free memory) +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window +RLAPI char **GetDroppedFiles(int *count); // Get dropped files names (memory should be freed) +RLAPI void ClearDroppedFiles(void); // Clear dropped files paths buffer (free memory) +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) + +RLAPI unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength); // Compress data (DEFLATE algorithm) +RLAPI unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength); // Decompress data (DEFLATE algorithm) + +// Persistent storage management +RLAPI bool SaveStorageValue(unsigned int position, int value); // Save integer value to storage file (to defined position), returns true on success +RLAPI int LoadStorageValue(unsigned int position); // Load integer value from storage file (from defined position) + +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Detect if a key has been pressed once +RLAPI bool IsKeyDown(int key); // Detect if a key is being pressed +RLAPI bool IsKeyReleased(int key); // Detect if a key has been released once +RLAPI bool IsKeyUp(int key); // Detect if a key is NOT being pressed +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available +RLAPI bool IsGamepadName(int gamepad, const char *name); // Check gamepad name (if available) +RLAPI const char *GetGamepadName(int gamepad); // Return gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Detect if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Detect if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Detect if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Return gamepad axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Return axis movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Detect if a mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Detect if a mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Detect if a mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Detect if a mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Returns mouse position X +RLAPI int GetMouseY(void); // Returns mouse position Y +RLAPI Vector2 GetMousePosition(void); // Returns mouse position XY +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Returns mouse wheel movement Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Returns touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Returns touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Returns touch position XY for a touch point index (relative to screen size) + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: gestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI int GetTouchPointsCount(void); // Get touch points count +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: camera) +//------------------------------------------------------------------------------------ +RLAPI void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) +RLAPI void UpdateCamera(Camera *camera); // Update camera position for selected mode + +RLAPI void SetCameraPanControl(int keyPan); // Set camera pan key to combine with mouse movement (free camera) +RLAPI void SetCameraAltControl(int keyAlt); // Set camera alt key to combine with mouse movement (free camera) +RLAPI void SetCameraSmoothZoomControl(int keySmoothZoom); // Set camera smooth zoom key to combine with mouse (free camera) +RLAPI void SetCameraMoveControls(int keyFront, int keyBack, int keyRight, int keyLeft, int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel (Vector version) +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (Vector version) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line defining thickness +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line using cubic-bezier curves in-out +RLAPI void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color); //Draw line using quadratic bezier curves with a control point +RLAPI void DrawLineStrip(Vector2 *points, int pointsCount, Color color); // Draw lines sequence +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color); // Draw rectangle outline with extended parameters +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) +RLAPI void DrawTriangleFan(Vector2 *points, int pointsCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: This functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. ".png" +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientV(int width, int height, Color top, Color bottom); // Generate image: vertical gradient +RLAPI Image GenImageGradientH(int width, int height, Color left, Color right); // Generate image: horizontal gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm. Bigger tileSize means bigger cells + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Generate all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorsCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw circle within an image (Vector version) +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data +RLAPI Image GetTextureData(Texture2D texture); // Get pixel data from GPU texture and return an Image +RLAPI Image GetScreenData(void); // Get pixel data from screen buffer and return an Image (screenshot) + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint); // Draw texture quad with tiling and offset parameters +RLAPI void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint); // Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely +RLAPI void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointsCount, Color tint); // Draw a textured polygon + +// Color/pixel related functions +RLAPI Color Fade(Color color, float alpha); // Returns color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Returns hexadecimal value for a Color +RLAPI Vector4 ColorNormalize(Color color); // Returns Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Returns Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Returns HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Returns a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color ColorAlpha(Color color, float alpha); // Returns color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Returns src alpha-blended into dst color with tint +RLAPI Color GetColor(int hexValue); // Get Color structure from hexadecimal value +RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount); // Load font from file with extended parameters +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount); // Load font from memory buffer, fileType refers to extension: i.e. ".ttf" +RLAPI CharInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const CharInfo *chars, Rectangle **recs, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(CharInfo *chars, int charsCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint); // Draw text using font inside rectangle limits +RLAPI void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, + int selectStart, int selectLength, Color selectTint, Color selectBackTint); // Draw text using font inside rectangle limits with support for text selection +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) + +// Text misc. functions +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get index position for a unicode character on font + +// Text strings management functions (no utf8 strings, only byte chars) +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI char *TextReplace(char *text, const char *replace, const char *by); // Replace text string (memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (memory must be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) +RLAPI char *TextToUtf8(int *codepoints, int length); // Encode text codepoint into utf8 text (memory must be freed!) + +// UTF8 text strings management functions +RLAPI int *GetCodepoints(const char *text, int *count); // Get all codepoints in a string, codepoints count returned by parameters +RLAPI int GetCodepointsCount(const char *text); // Get total number of characters (codepoints) in a UTF8 encoded string +RLAPI int GetNextCodepoint(const char *text, int *bytesProcessed); // Returns next codepoint in a UTF8 encoded string; 0x3f('?') is returned on failure +RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength); // Encode codepoint into utf8 text (char array length returned as parameter) + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleStrip3D(Vector3 *points, int pointsCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color); // Draw cube textured +RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere +RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters +RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model loading/unloading functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI void UnloadModelKeepMeshes(Model model); // Unload model (but not meshes) from memory (RAM and/or VRAM) + +// Mesh loading/unloading functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation* animations, unsigned int count); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Mesh manipulation functions +RLAPI BoundingBox MeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void MeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI void MeshBinormals(Mesh *mesh); // Compute mesh binormals + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 center, float size, Color tint); // Draw a billboard texture defined by source + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Detect collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Detect collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Detect collision between box and sphere +RLAPI bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius); // Detect collision between ray and sphere +RLAPI bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint); // Detect collision between ray and sphere, returns collision point +RLAPI bool CheckCollisionRayBox(Ray ray, BoundingBox box); // Detect collision between ray and box +RLAPI RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayHitInfo GetCollisionRayModel(Ray ray, Model model); // Get collision info between ray and model +RLAPI RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight); // Get collision info between ray and ground plane (Y-normal plane) + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +RLAPI void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +RLAPI int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel +RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a floats array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize); // Load music stream from data +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Init audio stream (to stream raw audio pcm data) +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data +RLAPI void CloseAudioStream(AudioStream stream); // Close audio stream and free memory +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H diff --git a/raylib_pi4_test/raylib.ico b/raylib_pi4_test/raylib.ico new file mode 100644 index 0000000..0cedcc5 Binary files /dev/null and b/raylib_pi4_test/raylib.ico differ diff --git a/raylib_pi4_test/raylib.rc b/raylib_pi4_test/raylib.rc new file mode 100644 index 0000000..4c500b2 --- /dev/null +++ b/raylib_pi4_test/raylib.rc @@ -0,0 +1,27 @@ +GLFW_ICON ICON "raylib.ico" + +1 VERSIONINFO +FILEVERSION 3,7,0,0 +PRODUCTVERSION 3,7,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + //BLOCK "080904E4" // English UK + BLOCK "040904E4" // English US + BEGIN + //VALUE "CompanyName", "raylib technologies" + VALUE "FileDescription", "raylib application (www.raylib.com)" + VALUE "FileVersion", "3.7.0" + VALUE "InternalName", "raylib app" + VALUE "LegalCopyright", "(c) 2021 Ramon Santamaria (@raysan5)" + //VALUE "OriginalFilename", "raylib_app.exe" + VALUE "ProductName", "raylib app" + VALUE "ProductVersion", "3.7.0" + END + END + BLOCK "VarFileInfo" + BEGIN + //VALUE "Translation", 0x809, 1252 // English UK + VALUE "Translation", 0x409, 1252 // English US + END +END diff --git a/raylib_pi4_test/raylib.rc.data b/raylib_pi4_test/raylib.rc.data new file mode 100644 index 0000000..4da6b88 Binary files /dev/null and b/raylib_pi4_test/raylib.rc.data differ diff --git a/raylib_pi4_test/raymath.h b/raylib_pi4_test/raymath.h new file mode 100644 index 0000000..e396d5a --- /dev/null +++ b/raylib_pi4_test/raymath.h @@ -0,0 +1,1546 @@ +/********************************************************************************************** +* +* raymath v1.2 - Math functions to work with Vector3, Matrix and Quaternions +* +* CONFIGURATION: +* +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYMATH_HEADER_ONLY +* Define static inline functions code, so #include header suffices for use. +* This may use up lots of memory. +* +* #define RAYMATH_STANDALONE +* Avoid raylib.h header inclusion in this file. +* Vector3 and Matrix data types are defined internally in raymath module. +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +//#define RAYMATH_STANDALONE // NOTE: To use raymath as standalone lib, just uncomment this line +//#define RAYMATH_HEADER_ONLY // NOTE: To compile functions as static inline, uncomment this line + +#ifndef RAYMATH_STANDALONE + #include "raylib.h" // Required for structs: Vector3, Matrix +#endif + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_HEADER_ONLY) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_HEADER_ONLY is contradictory" +#endif + +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMDEF __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll). + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMDEF __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RMDEF extern inline // Provide external definition + #endif +#elif defined(RAYMATH_HEADER_ONLY) + #define RMDEF static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMDEF static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMDEF inline // Functions may be inlined or external definition used + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Return float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Return float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +#if defined(RAYMATH_STANDALONE) + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Vector4 type + typedef struct Vector4 { + float x; + float y; + float z; + float w; + } Vector4; + + // Quaternion type + typedef Vector4 Quaternion; + + // Matrix type (OpenGL style 4x4 - right handed, column major) + typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; + } Matrix; +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +typedef struct float3 { float v[3]; } float3; +typedef struct float16 { float v[16]; } float16; + +#include // Required for: sinf(), cosf(), sqrtf(), tan(), fabs() + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMDEF float Clamp(float value, float min, float max) +{ + const float res = value < min ? min : value; + return res > max ? max : res; +} + +// Calculate linear interpolation between two floats +RMDEF float Lerp(float start, float end, float amount) +{ + return start + amount*(end - start); +} + +// Normalize input value within input range +RMDEF float Normalize(float value, float start, float end) +{ + return (value - start)/(end - start); +} + +// Remap input value within input range to output range +RMDEF float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + return (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMDEF Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + return result; +} + +// Vector with components value 1.0f +RMDEF Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + return result; +} + +// Add two vectors (v1 + v2) +RMDEF Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + return result; +} + +// Add vector and float value +RMDEF Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + return result; +} + +// Subtract two vectors (v1 - v2) +RMDEF Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + return result; +} + +// Subtract vector by float value +RMDEF Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + return result; +} + +// Calculate vector length +RMDEF float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + return result; +} + +// Calculate vector square length +RMDEF float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + return result; +} + +// Calculate two vectors dot product +RMDEF float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + return result; +} + +// Calculate distance between two vectors +RMDEF float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + return result; +} + +// Calculate angle from two vectors in X-axis +RMDEF float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); + if (result < 0) result += 360.0f; + return result; +} + +// Scale vector (multiply by value) +RMDEF Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + return result; +} + +// Multiply vector by vector +RMDEF Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + return result; +} + +// Negate vector +RMDEF Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + return result; +} + +// Divide vector by vector +RMDEF Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + return result; +} + +// Normalize provided vector +RMDEF Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = Vector2Scale(v, 1/Vector2Length(v)); + return result; +} + +// Calculate linear interpolation between two vectors +RMDEF Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMDEF Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = Vector2DotProduct(v, normal); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Rotate Vector by float in Degrees. +RMDEF Vector2 Vector2Rotate(Vector2 v, float degs) +{ + float rads = degs*DEG2RAD; + Vector2 result = {v.x*cosf(rads) - v.y*sinf(rads) , v.x*sinf(rads) + v.y*cosf(rads) }; + return result; +} + +// Move Vector towards target +RMDEF Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) result = target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMDEF Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + return result; +} + +// Vector with components value 1.0f +RMDEF Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + return result; +} + +// Add two vectors +RMDEF Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + return result; +} + +// Add vector and float value +RMDEF Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + return result; +} + +// Subtract two vectors +RMDEF Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + return result; +} + +// Subtract vector by float value +RMDEF Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + return result; +} + +// Multiply vector by scalar +RMDEF Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + return result; +} + +// Multiply vector by vector +RMDEF Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + return result; +} + +// Calculate two vectors cross product +RMDEF Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + return result; +} + +// Calculate one vector perpendicular vector +RMDEF Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = (float) fabs(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabs(v.y) < min) + { + min = (float) fabs(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabs(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + result = Vector3CrossProduct(v, cardinalAxis); + + return result; +} + +// Calculate vector length +RMDEF float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + return result; +} + +// Calculate vector square length +RMDEF float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + return result; +} + +// Calculate two vectors dot product +RMDEF float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + return result; +} + +// Calculate distance between two vectors +RMDEF float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + float result = sqrtf(dx*dx + dy*dy + dz*dz); + return result; +} + +// Negate provided vector (invert direction) +RMDEF Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + return result; +} + +// Divide vector by vector +RMDEF Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + return result; +} + +// Normalize provided vector +RMDEF Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length, ilength; + length = Vector3Length(v); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMDEF void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + *v1 = Vector3Normalize(*v1); + Vector3 vn = Vector3CrossProduct(*v1, *v2); + vn = Vector3Normalize(vn); + *v2 = Vector3CrossProduct(vn, *v1); +} + +// Transforms a Vector3 by a given Matrix +RMDEF Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMDEF Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Calculate linear interpolation between two vectors +RMDEF Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate reflected vector to normal +RMDEF Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*( DotProduct[ I,N] )) + + Vector3 result = { 0 }; + + float dotProduct = Vector3DotProduct(v, normal); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Return min value for each pair of components +RMDEF Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Return max value for each pair of components +RMDEF Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMDEF Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + //Vector v0 = b - a, v1 = c - a, v2 = p - a; + + Vector3 v0 = Vector3Subtract(b, a); + Vector3 v1 = Vector3Subtract(c, a); + Vector3 v2 = Vector3Subtract(p, a); + float d00 = Vector3DotProduct(v0, v0); + float d01 = Vector3DotProduct(v0, v1); + float d11 = Vector3DotProduct(v1, v1); + float d20 = Vector3DotProduct(v2, v0); + float d21 = Vector3DotProduct(v2, v1); + + float denom = d00*d11 - d01*d01; + + Vector3 result = { 0 }; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Returns Vector3 as float array +RMDEF float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMDEF float MatrixDeterminant(Matrix mat) +{ + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + return result; +} + +// Returns the trace of the matrix (sum of the values along the diagonal) +RMDEF float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + return result; +} + +// Transposes provided matrix +RMDEF Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMDEF Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Normalize provided matrix +RMDEF Matrix MatrixNormalize(Matrix mat) +{ + Matrix result = { 0 }; + + float det = MatrixDeterminant(mat); + + result.m0 = mat.m0/det; + result.m1 = mat.m1/det; + result.m2 = mat.m2/det; + result.m3 = mat.m3/det; + result.m4 = mat.m4/det; + result.m5 = mat.m5/det; + result.m6 = mat.m6/det; + result.m7 = mat.m7/det; + result.m8 = mat.m8/det; + result.m9 = mat.m9/det; + result.m10 = mat.m10/det; + result.m11 = mat.m11/det; + result.m12 = mat.m12/det; + result.m13 = mat.m13/det; + result.m14 = mat.m14/det; + result.m15 = mat.m15/det; + + return result; +} + +// Returns identity matrix +RMDEF Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMDEF Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = MatrixIdentity(); + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMDEF Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = MatrixIdentity(); + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Returns two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMDEF Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Returns translation matrix +RMDEF Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMDEF Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Returns x-rotation matrix (angle in radians) +RMDEF Matrix MatrixRotateX(float angle) +{ + Matrix result = MatrixIdentity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = -sinres; + result.m9 = sinres; + result.m10 = cosres; + + return result; +} + +// Returns y-rotation matrix (angle in radians) +RMDEF Matrix MatrixRotateY(float angle) +{ + Matrix result = MatrixIdentity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = sinres; + result.m8 = -sinres; + result.m10 = cosres; + + return result; +} + +// Returns z-rotation matrix (angle in radians) +RMDEF Matrix MatrixRotateZ(float angle) +{ + Matrix result = MatrixIdentity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = -sinres; + result.m4 = sinres; + result.m5 = cosres; + + return result; +} + + +// Returns xyz-rotation matrix (angles in radians) +RMDEF Matrix MatrixRotateXYZ(Vector3 ang) +{ + Matrix result = MatrixIdentity(); + + float cosz = cosf(-ang.z); + float sinz = sinf(-ang.z); + float cosy = cosf(-ang.y); + float siny = sinf(-ang.y); + float cosx = cosf(-ang.x); + float sinx = sinf(-ang.x); + + result.m0 = cosz*cosy; + result.m4 = (cosz*siny*sinx) - (sinz*cosx); + result.m8 = (cosz*siny*cosx) + (sinz*sinx); + + result.m1 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m9 = (sinz*siny*cosx) - (cosz*sinx); + + result.m2 = -siny; + result.m6 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Returns zyx-rotation matrix (angles in radians) +RMDEF Matrix MatrixRotateZYX(Vector3 ang) +{ + Matrix result = { 0 }; + + float cz = cosf(ang.z); + float sz = sinf(ang.z); + float cy = cosf(ang.y); + float sy = sinf(ang.y); + float cx = cosf(ang.x); + float sx = sinf(ang.x); + + result.m0 = cz*cy; + result.m1 = cz*sy*sx - cx*sz; + result.m2 = sz*sx + cz*cx*sy; + result.m3 = 0; + + result.m4 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m6 = cx*sz*sy - cz*sx; + result.m7 = 0; + + result.m8 = -sy; + result.m9 = cy*sx; + result.m10 = cy*cx; + result.m11 = 0; + + result.m12 = 0; + result.m13 = 0; + result.m14 = 0; + result.m15 = 1; + + return result; +} + +// Returns scaling matrix +RMDEF Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Returns perspective projection matrix +RMDEF Matrix MatrixFrustum(double left, double right, double bottom, double top, double near, double far) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(far - near); + + result.m0 = ((float) near*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float) near*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)far + (float)near)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)far*(float)near*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Returns perspective projection matrix +// NOTE: Angle should be provided in radians +RMDEF Matrix MatrixPerspective(double fovy, double aspect, double near, double far) +{ + double top = near*tan(fovy*0.5); + double right = top*aspect; + Matrix result = MatrixFrustum(-right, right, -top, top, near, far); + + return result; +} + +// Returns orthographic projection matrix +RMDEF Matrix MatrixOrtho(double left, double right, double bottom, double top, double near, double far) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(far - near); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)far + (float)near)/fn; + result.m15 = 1.0f; + + return result; +} + +// Returns camera look-at matrix (view matrix) +RMDEF Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + Vector3 z = Vector3Subtract(eye, target); + z = Vector3Normalize(z); + Vector3 x = Vector3CrossProduct(up, z); + x = Vector3Normalize(x); + Vector3 y = Vector3CrossProduct(z, x); + + result.m0 = x.x; + result.m1 = y.x; + result.m2 = z.x; + result.m3 = 0.0f; + result.m4 = x.y; + result.m5 = y.y; + result.m6 = z.y; + result.m7 = 0.0f; + result.m8 = x.z; + result.m9 = y.z; + result.m10 = z.z; + result.m11 = 0.0f; + result.m12 = -Vector3DotProduct(x, eye); + result.m13 = -Vector3DotProduct(y, eye); + result.m14 = -Vector3DotProduct(z, eye); + result.m15 = 1.0f; + + return result; +} + +// Returns float array of matrix data +RMDEF float16 MatrixToFloatV(Matrix mat) +{ + float16 buffer = { 0 }; + + buffer.v[0] = mat.m0; + buffer.v[1] = mat.m1; + buffer.v[2] = mat.m2; + buffer.v[3] = mat.m3; + buffer.v[4] = mat.m4; + buffer.v[5] = mat.m5; + buffer.v[6] = mat.m6; + buffer.v[7] = mat.m7; + buffer.v[8] = mat.m8; + buffer.v[9] = mat.m9; + buffer.v[10] = mat.m10; + buffer.v[11] = mat.m11; + buffer.v[12] = mat.m12; + buffer.v[13] = mat.m13; + buffer.v[14] = mat.m14; + buffer.v[15] = mat.m15; + + return buffer; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMDEF Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + return result; +} + +// Add quaternion and float value +RMDEF Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + return result; +} + +// Subtract two quaternions +RMDEF Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + return result; +} + +// Subtract quaternion and float value +RMDEF Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + return result; +} + +// Returns identity quaternion +RMDEF Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + return result; +} + +// Computes the length of a quaternion +RMDEF float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + return result; +} + +// Normalize provided quaternion +RMDEF Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length, ilength; + length = QuaternionLength(q); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMDEF Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + float length = QuaternionLength(q); + float lengthSq = length*length; + + if (lengthSq != 0.0) + { + float i = 1.0f/lengthSq; + + result.x *= -i; + result.y *= -i; + result.z *= -i; + result.w *= i; + } + + return result; +} + +// Calculate two quaternion multiplication +RMDEF Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMDEF Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + float qax = q.x, qay = q.y, qaz = q.z, qaw = q.w; + + result.x = qax*mul + qaw*mul + qay*mul - qaz*mul; + result.y = qay*mul + qaw*mul + qaz*mul - qax*mul; + result.z = qaz*mul + qaw*mul + qax*mul - qay*mul; + result.w = qaw*mul - qax*mul - qay*mul - qaz*mul; + + return result; +} + +// Divide two quaternions +RMDEF Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + return result; +} + +// Calculate linear interpolation between two quaternions +RMDEF Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMDEF Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = QuaternionLerp(q1, q2, amount); + result = QuaternionNormalize(result); + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMDEF Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabs(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabs(sinHalfTheta) < 0.001f) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMDEF Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = Vector3DotProduct(from, to); + Vector3 cross = Vector3CrossProduct(from, to); + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = 1.0f + cos2Theta; // NOTE: Added QuaternioIdentity() + + // Normalize to essentially nlerp the original and identity to 0.5 + result = QuaternionNormalize(result); + + // Above lines are equivalent to: + //Quaternion result = QuaternionNlerp(q, QuaternionIdentity(), 0.5f); + + return result; +} + +// Returns a quaternion for a given rotation matrix +RMDEF Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + if ((mat.m0 > mat.m5) && (mat.m0 > mat.m10)) + { + float s = sqrtf(1.0f + mat.m0 - mat.m5 - mat.m10)*2; + + result.x = 0.25f*s; + result.y = (mat.m4 + mat.m1)/s; + result.z = (mat.m2 + mat.m8)/s; + result.w = (mat.m9 - mat.m6)/s; + } + else if (mat.m5 > mat.m10) + { + float s = sqrtf(1.0f + mat.m5 - mat.m0 - mat.m10)*2; + result.x = (mat.m4 + mat.m1)/s; + result.y = 0.25f*s; + result.z = (mat.m9 + mat.m6)/s; + result.w = (mat.m2 - mat.m8)/s; + } + else + { + float s = sqrtf(1.0f + mat.m10 - mat.m0 - mat.m5)*2; + result.x = (mat.m2 + mat.m8)/s; + result.y = (mat.m9 + mat.m6)/s; + result.z = 0.25f*s; + result.w = (mat.m4 - mat.m1)/s; + } + + return result; +} + +// Returns a matrix for a given quaternion +RMDEF Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = MatrixIdentity(); + + float a2 = 2*(q.x*q.x), b2=2*(q.y*q.y), c2=2*(q.z*q.z); //, d2=2*(q.w*q.w); + + float ab = 2*(q.x*q.y), ac=2*(q.x*q.z), bc=2*(q.y*q.z); + float ad = 2*(q.x*q.w), bd=2*(q.y*q.w), cd=2*(q.z*q.w); + + result.m0 = 1 - b2 - c2; + result.m1 = ab - cd; + result.m2 = ac + bd; + + result.m4 = ab + cd; + result.m5 = 1 - a2 - c2; + result.m6 = bc - ad; + + result.m8 = ac - bd; + result.m9 = bc + ad; + result.m10 = 1 - a2 - b2; + + return result; +} + +// Returns rotation quaternion for an angle and axis +// NOTE: angle must be provided in radians +RMDEF Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + if (Vector3Length(axis) != 0.0f) + + angle *= 0.5f; + + axis = Vector3Normalize(axis); + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + result = QuaternionNormalize(result); + + return result; +} + +// Returns the rotation angle and axis for a given quaternion +RMDEF void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabs(q.w) > 1.0f) q = QuaternionNormalize(q); + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > 0.0001f) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Returns the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMDEF Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion q = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + q.x = x1*y0*z0 - x0*y1*z1; + q.y = x0*y1*z0 + x1*y0*z1; + q.z = x0*y0*z1 - x1*y1*z0; + q.w = x0*y0*z0 + x1*y1*z1; + + return q; +} + +// Return the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in degrees +RMDEF Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1)*RAD2DEG; + + // pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0)*RAD2DEG; + + // yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1)*RAD2DEG; + + return result; +} + +// Transform a quaternion given a transformation matrix +RMDEF Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +// Projects a Vector3 from screen space into object space +RMDEF Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + // Calculate unproject matrix (multiply view patrix by projection matrix) and invert it + Matrix matViewProj = MatrixMultiply(view, projection); + matViewProj = MatrixInvert(matViewProj); + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unproject matrix + quat = QuaternionTransform(quat, matViewProj); + + // Normalized world points in vectors + result.x = quat.x/quat.w; + result.y = quat.y/quat.w; + result.z = quat.z/quat.w; + + return result; +} + +#endif // RAYMATH_H diff --git a/raylib_pi4_test/rglfw.c b/raylib_pi4_test/rglfw.c new file mode 100644 index 0000000..5728fa6 --- /dev/null +++ b/raylib_pi4_test/rglfw.c @@ -0,0 +1,120 @@ +/********************************************************************************************** +* +* rglfw - raylib GLFW single file compilation +* +* This file includes latest GLFW sources (https://github.com/glfw/glfw) to be compiled together +* with raylib for all supported platforms, this way, no external dependencies are required. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2017-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +//#define _GLFW_BUILD_DLL // To build shared version +//http://www.glfw.org/docs/latest/compile.html#compile_manual + +#if defined(_WIN32) + #define _GLFW_WIN32 +#endif +#if defined(__linux__) + #if !defined(_GLFW_WAYLAND) // Required for Wayland windowing + #define _GLFW_X11 + #endif +#endif +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + #define _GLFW_X11 +#endif +#if defined(__APPLE__) + #define _GLFW_COCOA + #define _GLFW_USE_MENUBAR // To create and populate the menu bar when the first window is created + #define _GLFW_USE_RETINA // To have windows use the full resolution of Retina displays +#endif +#if defined(__TINYC__) + #define _WIN32_WINNT_WINXP 0x0501 +#endif + +// NOTE: _GLFW_MIR experimental platform not supported at this moment + +#include "external/glfw/src/context.c" +#include "external/glfw/src/init.c" +#include "external/glfw/src/input.c" +#include "external/glfw/src/monitor.c" +#include "external/glfw/src/vulkan.c" +#include "external/glfw/src/window.c" + +#if defined(_WIN32) + #include "external/glfw/src/win32_init.c" + #include "external/glfw/src/win32_joystick.c" + #include "external/glfw/src/win32_monitor.c" + #include "external/glfw/src/win32_time.c" + #include "external/glfw/src/win32_thread.c" + #include "external/glfw/src/win32_window.c" + #include "external/glfw/src/wgl_context.c" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif + +#if defined(__linux__) + #if defined(_GLFW_WAYLAND) + #include "external/glfw/src/wl_init.c" + #include "external/glfw/src/wl_monitor.c" + #include "external/glfw/src/wl_window.c" + #include "external/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c" + #include "external/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c" + #endif + #if defined(_GLFW_X11) + #include "external/glfw/src/x11_init.c" + #include "external/glfw/src/x11_monitor.c" + #include "external/glfw/src/x11_window.c" + #include "external/glfw/src/glx_context.c" + #endif + + #include "external/glfw/src/linux_joystick.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/posix_time.c" + #include "external/glfw/src/xkb_unicode.c" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined( __NetBSD__) || defined(__DragonFly__) + #include "external/glfw/src/x11_init.c" + #include "external/glfw/src/x11_monitor.c" + #include "external/glfw/src/x11_window.c" + #include "external/glfw/src/xkb_unicode.c" + // TODO: Joystick implementation + #include "external/glfw/src/null_joystick.c" + #include "external/glfw/src/posix_time.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/glx_context.c" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif + +#if defined(__APPLE__) + #include "external/glfw/src/cocoa_init.m" + #include "external/glfw/src/cocoa_joystick.m" + #include "external/glfw/src/cocoa_monitor.m" + #include "external/glfw/src/cocoa_window.m" + #include "external/glfw/src/cocoa_time.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/nsgl_context.m" + #include "external/glfw/src/egl_context.c" + #include "external/glfw/src/osmesa_context.c" +#endif diff --git a/raylib_pi4_test/rlgl.h b/raylib_pi4_test/rlgl.h new file mode 100644 index 0000000..bb0bc23 --- /dev/null +++ b/raylib_pi4_test/rlgl.h @@ -0,0 +1,4076 @@ +/********************************************************************************************** +* +* rlgl v3.7 - raylib OpenGL abstraction layer +* +* rlgl is a wrapper for multiple OpenGL versions (1.1, 2.1, 3.3 Core, ES 2.0) to +* pseudo-OpenGL 1.1 style functions (rlVertex, rlTranslate, rlRotate...). +* +* When chosing an OpenGL version greater than OpenGL 1.1, rlgl stores vertex data on internal +* VBO buffers (and VAOs if available). It requires calling 3 functions: +* rlglInit() - Initialize internal buffers and auxiliary resources +* rlglClose() - De-initialize internal buffers data and other auxiliar resources +* +* CONFIGURATION: +* +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_ES2 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RLGL_STANDALONE +* Use rlgl as standalone library (no raylib dependency) +* +* #define SUPPORT_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* DEPENDENCIES: +* raymath - 3D math functionality (Vector3, Matrix, Quaternion) +* GLAD - OpenGL extensions loading (OpenGL 3.3 Core only) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLGL_H +#define RLGL_H + +#if defined(RLGL_STANDALONE) + #define RAYMATH_STANDALONE + #define RAYMATH_HEADER_ONLY + + #define RLAPI // We are building or using rlgl as a static library (or Linux shared library) + + #if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building raylib as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #endif + #endif + + // Support TRACELOG macros + #if !defined(TRACELOG) + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 + #endif + + // Allow custom memory allocators + #ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) + #endif + #ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) + #endif + #ifndef RL_REALLOC + #define RL_REALLOC(n,sz) realloc(n,sz) + #endif + #ifndef RL_FREE + #define RL_FREE(p) free(p) + #endif +#else + #include "raylib.h" // Required for: Shader, Texture2D +#endif + +#include "raymath.h" // Required for: Vector3, Matrix + +// Security check in case no GRAPHICS_API_OPENGL_* defined +#if !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_ES2) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Security check in case multiple GRAPHICS_API_OPENGL_* defined +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_21) + #undef GRAPHICS_API_OPENGL_21 + #endif + #if defined(GRAPHICS_API_OPENGL_33) + #undef GRAPHICS_API_OPENGL_33 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + #undef GRAPHICS_API_OPENGL_ES2 + #endif +#endif + +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 +#endif + +#define SUPPORT_RENDER_TEXTURES_HINT + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Default internal render batch limits +#ifndef DEFAULT_BATCH_BUFFER_ELEMENTS + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define DEFAULT_BATCH_BUFFER_ELEMENTS 8192 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + #define DEFAULT_BATCH_BUFFER_ELEMENTS 2048 + #endif +#endif +#ifndef DEFAULT_BATCH_BUFFERS + #define DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#endif +#ifndef DEFAULT_BATCH_DRAWCALLS + #define DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +#endif +#ifndef MAX_BATCH_ACTIVE_TEXTURES + #define MAX_BATCH_ACTIVE_TEXTURES 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) +#endif + +// Internal Matrix stack +#ifndef MAX_MATRIX_STACK_SIZE + #define MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack +#endif + +// Vertex buffers id limit +#ifndef MAX_MESH_VERTEX_BUFFERS + #define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#endif + +// Shader and material limits +#ifndef MAX_SHADER_LOCATIONS + #define MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#endif +#ifndef MAX_MATERIAL_MAPS + #define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported +#endif + +// Projection matrix culling +#ifndef RL_CULL_DISTANCE_NEAR + #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance +#endif +#ifndef RL_CULL_DISTANCE_FAR + #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance +#endif + +// Texture parameters (equivalent to OpenGL defines) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER + +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) + +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef enum { OPENGL_11 = 1, OPENGL_21, OPENGL_33, OPENGL_ES_20 } GlVersion; + +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, + RL_ATTACHMENT_COLOR_CHANNEL1, + RL_ATTACHMENT_COLOR_CHANNEL2, + RL_ATTACHMENT_COLOR_CHANNEL3, + RL_ATTACHMENT_COLOR_CHANNEL4, + RL_ATTACHMENT_COLOR_CHANNEL5, + RL_ATTACHMENT_COLOR_CHANNEL6, + RL_ATTACHMENT_COLOR_CHANNEL7, + RL_ATTACHMENT_DEPTH = 100, + RL_ATTACHMENT_STENCIL = 200, +} FramebufferAttachType; + +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z, + RL_ATTACHMENT_TEXTURE2D = 100, + RL_ATTACHMENT_RENDERBUFFER = 200, +} FramebufferAttachTextureType; + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct VertexBuffer { + int elementsCount; // Number of elements in the buffer (QUADS) + + int vCounter; // Vertex position counter to process (and draw) from full buffer + int tcCounter; // Vertex texcoord counter to process (and draw) from full buffer + int cCounter; // Vertex color counter to process (and draw) from full buffer + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) +} VertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct DrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShader.id + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} DrawCall; + +// RenderBatch type +typedef struct RenderBatch { + int buffersCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + VertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + DrawCall *draws; // Draw calls array, depends on textureId + int drawsCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} RenderBatch; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, + SHADER_ATTRIB_VEC2, + SHADER_ATTRIB_VEC3, + SHADER_ATTRIB_VEC4 +} ShaderAttributeDataType; + +#if defined(RLGL_STANDALONE) + #ifndef __cplusplus + // Boolean type + typedef enum { false, true } bool; + #endif + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Texture type + // NOTE: Data stored in GPU memory + typedef struct Texture2D { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat) + } Texture2D; + + // Shader type (generic) + typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (MAX_SHADER_LOCATIONS) + } Shader; + + // TraceLog message types + typedef enum { + LOG_ALL, + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE + } TraceLogLevel; + + // Texture formats (support depends on OpenGL version) + typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_RGB565_BE, // 16 bpp (big endian) + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp + } PixelFormat; + + // Texture parameters: filter mode + // NOTE 1: Filtering considers mipmaps if available in the texture + // NOTE 2: Filter is accordingly set for minification and magnification + typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel aproximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x + } TextureFilter; + + // Texture parameters: wrap mode + typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode + } TextureWrap; + + // Color blending modes (pre-defined) + typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_CUSTOM // Belnd textures using custom src/dst factors (use SetBlendModeCustom()) + } BlendMode; + + // Shader location point type + typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, + SHADER_LOC_VERTEX_TEXCOORD01, + SHADER_LOC_VERTEX_TEXCOORD02, + SHADER_LOC_VERTEX_NORMAL, + SHADER_LOC_VERTEX_TANGENT, + SHADER_LOC_VERTEX_COLOR, + SHADER_LOC_MATRIX_MVP, + SHADER_LOC_MATRIX_MODEL, + SHADER_LOC_MATRIX_VIEW, + SHADER_LOC_MATRIX_NORMAL, + SHADER_LOC_MATRIX_PROJECTION, + SHADER_LOC_VECTOR_VIEW, + SHADER_LOC_COLOR_DIFFUSE, + SHADER_LOC_COLOR_SPECULAR, + SHADER_LOC_COLOR_AMBIENT, + SHADER_LOC_MAP_ALBEDO, // SHADER_LOC_MAP_DIFFUSE + SHADER_LOC_MAP_METALNESS, // SHADER_LOC_MAP_SPECULAR + SHADER_LOC_MAP_NORMAL, + SHADER_LOC_MAP_ROUGHNESS, + SHADER_LOC_MAP_OCCLUSION, + SHADER_LOC_MAP_EMISSION, + SHADER_LOC_MAP_HEIGHT, + SHADER_LOC_MAP_CUBEMAP, + SHADER_LOC_MAP_IRRADIANCE, + SHADER_LOC_MAP_PREFILTER, + SHADER_LOC_MAP_BRDF + } ShaderLocationIndex; + + #define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO + #define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + + // Shader uniform data types + typedef enum { + SHADER_UNIFORM_FLOAT = 0, + SHADER_UNIFORM_VEC2, + SHADER_UNIFORM_VEC3, + SHADER_UNIFORM_VEC4, + SHADER_UNIFORM_INT, + SHADER_UNIFORM_IVEC2, + SHADER_UNIFORM_IVEC3, + SHADER_UNIFORM_IVEC4, + SHADER_UNIFORM_SAMPLER2D + } ShaderUniformDataType; +#endif + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//------------------------------------------------------------------------------------ +// Functions Declaration - Matrix operations +//------------------------------------------------------------------------------------ +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop lattest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angleDeg, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(float *matf); // Multiply the current matrix by another matrix +RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area + +//------------------------------------------------------------------------------------ +// Functions Declaration - Vertex level operations +//------------------------------------------------------------------------------------ +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +//------------------------------------------------------------------------------------ +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom +//------------------------------------------------------------------------------------ + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id);// Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index);// Disable vertex attribute index +#if defined(GRAPHICS_API_OPENGL_11) +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); +RLAPI void rlDisableStatePointer(int vertexAttribType); +#endif + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap +RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer + +// General render state +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlDisableWireMode(void); // Disable wire mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) + +//------------------------------------------------------------------------------------ +// Functions Declaration - rlgl functionality +//------------------------------------------------------------------------------------ +// rlgl initialization functions +RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) +RLAPI void rlglClose(void); // De-inititialize rlgl (buffers, shaders, textures) +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) +RLAPI int rlGetVersion(void); // Returns current OpenGL version +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height + +RLAPI Shader rlGetShaderDefault(void); // Get default shader +RLAPI Texture2D rlGetTextureDefault(void); // Get default texture + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI RenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(RenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(RenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(RenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic); // Load a vertex buffer attribute +RLAPI unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic); // Load a new attributes element buffer +RLAPI void rlUpdateVertexBuffer(int bufferId, void *data, int dataSize, int offset); // Update GPU buffer with new data +RLAPI void rlUnloadVertexArray(unsigned int vaoId); +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer); +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value +RLAPI void rlDrawVertexArray(int offset, int count); +RLAPI void rlDrawVertexArrayElements(int offset, int count, void *buffer); +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances); + +// Textures management +RLAPI unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(void *data, int size, int format); // Load texture cubemap +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update GPU texture with new data +RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats +RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +RLAPI void rlGenerateMipmaps(Texture2D *texture); // Generate mipmap data for selected texture +RLAPI void *rlReadTexturePixels(Texture2D texture); // Read texture pixel data +RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) + +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(int width, int height); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU + +// Shaders management +RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: GL_VERTEX_SHADER, GL_FRAGMENT_SHADER) +RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform +RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(Shader shader); // Set shader currently active + +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering + +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad +#if defined(__cplusplus) +} +#endif + +#endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +#if !defined(RLGL_STANDALONE) + // Check if config flags have been externally provided on compilation line + #if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags + #endif + #include "raymath.h" // Required for: Vector3 and Matrix functions +#endif + +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(__APPLE__) + #include // OpenGL 1.1 library for OSX + #include + #else + // APIENTRY for OpenGL function pointer declarations is required + #ifndef APIENTRY + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include // OpenGL 1.1 library + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #if defined(__APPLE__) + #include // OpenGL 3 library for OSX + #include // OpenGL 3 extensions library for OSX + #else + #define GLAD_REALLOC RL_REALLOC + #define GLAD_FREE RL_FREE + + #define GLAD_IMPLEMENTATION + #if defined(RLGL_STANDALONE) + #include "glad.h" // GLAD extensions loading library, includes OpenGL headers + #else + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers + #endif + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define GL_GLEXT_PROTOTYPES + //#include // EGL library -> not required, platform layer + #include // OpenGL ES 2.0 library + #include // OpenGL ES 2.0 extensions library + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + //#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define glClearDepth glClearDepthf + #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER +#endif + +// Default shader vertex attribute names to set location points +#ifndef DEFAULT_SHADER_ATTRIB_NAME_POSITION + #define DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Binded by default to shader location: 0 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD + #define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Binded by default to shader location: 1 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_NORMAL + #define DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Binded by default to shader location: 2 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_COLOR + #define DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Binded by default to shader location: 3 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_TANGENT + #define DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Binded by default to shader location: 4 +#endif +#ifndef DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 + #define DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Binded by default to shader location: 5 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +typedef struct rlglData { + RenderBatch *currentBatch; // Current render batch + RenderBatch defaultBatch; // Default internal render batch + + struct { + int currentMatrixMode; // Current matrix mode + Matrix *currentMatrix; // Current matrix pointer + Matrix modelview; // Default modelview matrix + Matrix projection; // Default projection matrix + Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale + bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) + Matrix stack[MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop + int stackCounter; // Matrix stack counter + + unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[MAX_BATCH_ACTIVE_TEXTURES]; // Active texture ids to be enabled on batch drawing (0 active by default) + unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) + unsigned int defaultFShaderId; // Default fragment shader Id (used by default shader program) + Shader defaultShader; // Basic shader, support vertex color and diffuse texture + Shader currentShader; // Shader to be used on rendering (by default, defaultShader) + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + + int currentBlendMode; // Blending mode active + int glBlendSrcFactor; // Blending source factor + int glBlendDstFactor; // Blending destination factor + int glBlendEquation; // Blending equation + + int framebufferWidth; // Default framebuffer width + int framebufferHeight; // Default framebuffer height + + } State; // Renderer state + struct { + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_WEBGL_depth_texture, GL_OES_depth_texture) + bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) + bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) + bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) + + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) + int maxDepthBits; // Maximum bits for depth component + + } ExtSupported; // Extensions supported flags +} rlglData; + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static rlglData RLGL = { 0 }; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_ES2) +// NOTE: VAO functionality is exposed through extensions (OES) +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// NOTE: Instancing functionality could also be available through extension +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static void rlLoadShaderDefault(void); // Load default shader (RLGL.State.defaultShader) +static void rlUnloadShaderDefault(void); // Unload default shader (RLGL.State.defaultShader) +#if defined(SUPPORT_GL_DETAILS_INFO) +static char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif // SUPPORT_GL_DETAILS_INFO +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +#if defined(GRAPHICS_API_OPENGL_11) +static int rlGenerateMipmapsData(unsigned char *data, int baseWidth, int baseHeight); // Generate mipmaps data on CPU side +static Color *rlGenNextMipmapData(Color *srcData, int srcWidth, int srcHeight); // Generate next mipmap level on CPU side +#endif +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix operations +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlMatrixMode(int mode) +{ + switch (mode) + { + case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; + case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; + case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; + default: break; + } +} + +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + glFrustum(left, right, bottom, top, znear, zfar); +} + +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + glOrtho(left, right, bottom, top, znear, zfar); +} + +void rlPushMatrix(void) { glPushMatrix(); } +void rlPopMatrix(void) { glPopMatrix(); } +void rlLoadIdentity(void) { glLoadIdentity(); } +void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } +void rlRotatef(float angleDeg, float x, float y, float z) { glRotatef(angleDeg, x, y, z); } +void rlScalef(float x, float y, float z) { glScalef(x, y, z); } +void rlMultMatrixf(float *matf) { glMultMatrixf(matf); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Choose the current matrix to be transformed +void rlMatrixMode(int mode) +{ + if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; + else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; + //else if (mode == RL_TEXTURE) // Not supported + + RLGL.State.currentMatrixMode = mode; +} + +// Push the current matrix into RLGL.State.stack +void rlPushMatrix(void) +{ + if (RLGL.State.stackCounter >= MAX_MATRIX_STACK_SIZE) TRACELOG(LOG_ERROR, "RLGL: Matrix stack overflow (MAX_MATRIX_STACK_SIZE)"); + + if (RLGL.State.currentMatrixMode == RL_MODELVIEW) + { + RLGL.State.transformRequired = true; + RLGL.State.currentMatrix = &RLGL.State.transform; + } + + RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; + RLGL.State.stackCounter++; +} + +// Pop lattest inserted matrix from RLGL.State.stack +void rlPopMatrix(void) +{ + if (RLGL.State.stackCounter > 0) + { + Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; + *RLGL.State.currentMatrix = mat; + RLGL.State.stackCounter--; + } + + if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) + { + RLGL.State.currentMatrix = &RLGL.State.modelview; + RLGL.State.transformRequired = false; + } +} + +// Reset current matrix to identity matrix +void rlLoadIdentity(void) +{ + *RLGL.State.currentMatrix = MatrixIdentity(); +} + +// Multiply the current matrix by a translation matrix +void rlTranslatef(float x, float y, float z) +{ + Matrix matTranslation = MatrixTranslate(x, y, z); + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = MatrixMultiply(matTranslation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a rotation matrix +void rlRotatef(float angleDeg, float x, float y, float z) +{ + Matrix matRotation = MatrixIdentity(); + + Vector3 axis = (Vector3){ x, y, z }; + matRotation = MatrixRotate(Vector3Normalize(axis), angleDeg*DEG2RAD); + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = MatrixMultiply(matRotation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = MatrixScale(x, y, z); + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = MatrixMultiply(matScale, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(float *matf) +{ + // Matrix creation from array + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *RLGL.State.currentMatrix = MatrixMultiply(*RLGL.State.currentMatrix, mat); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + Matrix matPerps = MatrixFrustum(left, right, bottom, top, znear, zfar); + + *RLGL.State.currentMatrix = MatrixMultiply(*RLGL.State.currentMatrix, matPerps); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + // NOTE: If left-right and top-botton values are equal it could create + // a division by zero on MatrixOrtho(), response to it is platform/compiler dependant + Matrix matOrtho = MatrixOrtho(left, right, bottom, top, znear, zfar); + + *RLGL.State.currentMatrix = MatrixMultiply(*RLGL.State.currentMatrix, matOrtho); +} +#endif + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +void rlViewport(int x, int y, int width, int height) +{ + glViewport(x, y, width, height); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vertex level operations +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlBegin(int mode) +{ + switch (mode) + { + case RL_LINES: glBegin(GL_LINES); break; + case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; + case RL_QUADS: glBegin(GL_QUADS); break; + default: break; + } +} + +void rlEnd() { glEnd(); } +void rlVertex2i(int x, int y) { glVertex2i(x, y); } +void rlVertex2f(float x, float y) { glVertex2f(x, y); } +void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } +void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } +void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } +void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } +void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Initialize drawing mode (how to organize vertex) +void rlBegin(int mode) +{ + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode != mode) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawsCounter++; + } + } + + if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode = mode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount = 0; + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId = RLGL.State.defaultTextureId; + } +} + +// Finish vertex providing +void rlEnd(void) +{ + // Make sure vertexCount is the same for vertices, texcoords, colors and normals + // NOTE: In OpenGL 1.1, one glColor call can be made for all the subsequent glVertex calls + + // Make sure colors count match vertex count + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter != RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter) + { + int addColors = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter; + + for (int i = 0; i < addColors; i++) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 4]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 1] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 3]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 2] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 2]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 3] = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter - 1]; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter++; + } + } + + // Make sure texcoords count match vertex count + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter != RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter) + { + int addTexCoords = RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter; + + for (int i = 0; i < addTexCoords; i++) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter] = 0.0f; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter + 1] = 0.0f; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter++; + } + } + + // TODO: Make sure normals count match vertex count... if normals support is added in a future... :P + + // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + RLGL.currentBatch->currentDepth += (1.0f/20000.0f); + + // Verify internal buffers limits + // NOTE: This check is combined with usage of rlCheckRenderBatchLimit() + if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4 - 4)) + { + // WARNING: If we are between rlPushMatrix() and rlPopMatrix() and we need to force a rlDrawRenderBatch(), + // we need to call rlPopMatrix() before to recover *RLGL.State.currentMatrix (RLGL.State.modelview) for the next forced draw call! + // If we have multiple matrix pushed, it will require "RLGL.State.stackCounter" pops before launching the draw + for (int i = RLGL.State.stackCounter; i >= 0; i--) rlPopMatrix(); + rlDrawRenderBatch(RLGL.currentBatch); + } +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +void rlVertex3f(float x, float y, float z) +{ + Vector3 vec = { x, y, z }; + + // Transform provided vector if required + if (RLGL.State.transformRequired) vec = Vector3Transform(vec, RLGL.State.transform); + + // Verify that current vertex buffer elements limit has not been reached + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter < (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4)) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter] = vec.x; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + 1] = vec.y; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + 2] = vec.z; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter++; + + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount++; + } + else TRACELOG(LOG_ERROR, "RLGL: Batch elements overflow"); +} + +// Define one vertex (position) +void rlVertex2f(float x, float y) +{ + rlVertex3f(x, y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (position) +void rlVertex2i(int x, int y) +{ + rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (texture coordinate) +// NOTE: Texture coordinates are limited to QUADS only +void rlTexCoord2f(float x, float y) +{ + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter] = x; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter + 1] = y; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter++; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +void rlNormal3f(float x, float y, float z) +{ + // TODO: Normals usage... +} + +// Define one vertex (color) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter] = x; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 1] = y; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 2] = z; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 3] = w; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter++; +} + +// Define one vertex (color) +void rlColor4f(float r, float g, float b, float a) +{ + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Define one vertex (color) +void rlColor3f(float x, float y, float z) +{ + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +#endif + +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- + +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawsCounter++; + } + } + + if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture +void rlEnableTexture(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, id); +} + +// Disable texture +void rlDisableTexture(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnable(GL_TEXTURE_CUBE_MAP); // Core in OpenGL 1.4 + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisable(GL_TEXTURE_CUBE_MAP); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); + else TRACELOG(LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); +#endif + } + else glTexParameteri(GL_TEXTURE_2D, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(LOG_WARNING, "GL: Anisotropic filtering not supported"); +#endif + } break; + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + +// Enable rendering to texture (fbo) +void rlEnableFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); +#endif +} + +// Disable rendering to texture +void rlDisableFramebuffer(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Enable depth test +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } + +// Disable depth test +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Enable scissor test +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } + +// Scissor test +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } + +// Enable wire mode +void rlEnableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} +// Set the line drawing width +void rlSetLineWidth(float width) +{ + glLineWidth(width); +} + +// Get the line drawing width +float rlGetLineWidth(void) +{ + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} + +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); +#endif +} + +// Disable line aliasing +void rlDisableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); +#endif +} + +// Enable stereo rendering +void rlEnableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; +#endif +} + +// Disable stereo rendering +void rlDisableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; +#endif +} + +// Clear color buffer with color +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // Color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + glClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +void rlClearScreenBuffers(void) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Check and log OpenGL error codes +void rlCheckErrors() +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentBlendMode != mode) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case BLEND_CUSTOM: glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - rlgl functionality +//---------------------------------------------------------------------------------- + +// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states +void rlglInit(int width, int height) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + + if (RLGL.State.defaultTextureId != 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load default texture"); + + // Init default Shader (customized for GL 3.3 and ES2) + rlLoadShaderDefault(); // RLGL.State.defaultShader + RLGL.State.currentShader = RLGL.State.defaultShader; + + // Init default vertex arrays buffers + RLGL.defaultBatch = rlLoadRenderBatch(DEFAULT_BATCH_BUFFERS, DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.currentBatch = &RLGL.defaultBatch; + + // Init stack matrices (emulating OpenGL 1.1) + for (int i = 0; i < MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = MatrixIdentity(); + + // Init internal matrices + RLGL.State.transform = MatrixIdentity(); + RLGL.State.projection = MatrixIdentity(); + RLGL.State.modelview = MatrixIdentity(); + RLGL.State.currentMatrix = &RLGL.State.modelview; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + + // Initialize OpenGL default states + //---------------------------------------------------------- + // Init state: Depth test + glDepthFunc(GL_LEQUAL); // Type of depth testing to apply + glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) + glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + glCullFace(GL_BACK); // Cull the back face (default) + glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) + glEnable(GL_CULL_FACE); // Enable backface culling + + // Init state: Cubemap seamless +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + // Init state: Color hints (deprecated in OpenGL 3.0+) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation + glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Store screen size into global variables + RLGL.State.framebufferWidth = width; + RLGL.State.framebufferHeight = height; + + TRACELOG(LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +#endif + + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) +} + +// Vertex Buffer Object deinitialization (memory free) +void rlglClose(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader + + glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); +#endif +} + +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + #if !defined(__APPLE__) + if (!gladLoadGLLoader((GLADloadproc)loader)) TRACELOG(LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + #endif + + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(SUPPORT_GL_DETAILS_INFO) + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + char **extList = RL_MALLOC(sizeof(char *)*numExt); + TRACELOG(LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) + { + extList[i] = (char *)glGetStringi(GL_EXTENSIONS, i); + TRACELOG(LOG_INFO, " %s", extList[i]); + } + RL_FREE(extList); // Free extensions pointers +#endif + + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + #if !defined(__APPLE__) + // NOTE: With GLAD, we can check if an extension is supported using the GLAD_GL_xxx booleans + if (GLAD_GL_EXT_texture_compression_s3tc) RLGL.ExtSupported.texCompDXT = true; // Texture compression: DXT + if (GLAD_GL_ARB_ES3_compatibility) RLGL.ExtSupported.texCompETC2 = true; // Texture compression: ETC2/EAC + #endif +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES2) + // Get supported extensions list + GLint numExt = 0; + const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: We have to duplicate string because glGetString() returns a const string + int len = strlen(extensions) + 1; + char *extensionsDup = (char *)RL_CALLOC(len, sizeof(char)); + strcpy(extensionsDup, extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < len; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } + } + + TRACELOG(LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(SUPPORT_GL_DETAILS_INFO) + TRACELOG(LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(LOG_INFO, " %s", extList[i]); +#endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // Web ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else + { + if ((strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) && // Standard EXT + (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0)) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + + // Check depth texture support + if ((strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0)) RLGL.ExtSupported.texDepth = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if defined(SUPPORT_GL_DETAILS_INFO) + // Show some OpenGL GPU capabilities + TRACELOG(LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint format[32] = { 0 }; + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, format); + for (int i = 0; i < capability; i++) TRACELOG(LOG_INFO, " %s", rlGetCompressedFormatName(format[i])); + + /* + // Following capabilities are only supported by OpenGL 4.3 or greater + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); + */ +#else // SUPPORT_GL_DETAILS_INFO + + // Show some basic info about GL supported features + #if defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) TRACELOG(LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + #endif + if (RLGL.ExtSupported.texCompDXT) TRACELOG(LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(LOG_INFO, "GL: ASTC compressed textures supported"); +#endif // SUPPORT_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +} + +// Returns current OpenGL version +int rlGetVersion(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + return OPENGL_11; +#endif +#if defined(GRAPHICS_API_OPENGL_21) + #if defined(__APPLE__) + return OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX + #else + return OPENGL_21; + #endif +#elif defined(GRAPHICS_API_OPENGL_33) + return OPENGL_33; +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + return OPENGL_ES_20; +#endif +} + +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.framebufferWidth; +#else + return 0; +#endif +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.framebufferHeight; +#else + return 0; +#endif +} + +// Get default internal shader (simple texture + tint color) +Shader rlGetShaderDefault(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.defaultShader; +#else + Shader shader = { 0 }; + return shader; +#endif +} + +// Get default internal texture (white texture) +Texture2D rlGetTextureDefault(void) +{ + Texture2D texture = { 0 }; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + texture.id = RLGL.State.defaultTextureId; + texture.width = 1; + texture.height = 1; + texture.mipmaps = 1; + texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; +#endif + return texture; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +RenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + RenderBatch batch = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (VertexBuffer *)RL_MALLOC(sizeof(VertexBuffer)*numBuffers); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementsCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + batch.vertexBuffer[i].vCounter = 0; + batch.vertexBuffer[i].tcCounter = 0; + batch.vertexBuffer[i].cCounter = 0; + } + + TRACELOG(LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (DrawCall *)RL_MALLOC(DEFAULT_BATCH_DRAWCALLS*sizeof(DrawCall)); + + for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = MatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = MatrixIdentity(); + } + + batch.buffersCount = numBuffers; // Record buffer count + batch.drawsCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(RenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + glDisableVertexAttribArray(3); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.buffersCount; i++) + { + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +void rlDrawRenderBatch(RenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) + if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue. + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job. + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer(). + // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data. + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (batch->vertexBuffer[batch->currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyesCount = 1; + if (RLGL.State.stereoRender) eyesCount = 2; + + for (int eye = 0; eye < eyesCount; eye++) + { + if (eyesCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(MatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShader.id); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = MatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + glUniformMatrix4fv(RLGL.State.currentShader.locs[SHADER_LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShader.locs[SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShader.locs[SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawsCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and binded to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { +#if defined(GRAPHICS_API_OPENGL_33) + // We need to define the number of indices to be processed: quadsCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); +#endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counters for next frame + batch->vertexBuffer[batch->currentBuffer].vCounter = 0; + batch->vertexBuffer[batch->currentBuffer].tcCounter = 0; + batch->vertexBuffer[batch->currentBuffer].cCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawsCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->buffersCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(RenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + +// Check internal buffer overflow for a given number of vertex +// and force a RenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) +{ + bool overflow = false; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4)) + { + overflow = true; + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + } +#endif + + return overflow; +} + +// Textures data management +//----------------------------------------------------------------------------------------- +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount) +{ + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + unsigned int id = 0; + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) +#if defined(GRAPHICS_API_OPENGL_11) + if (format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#else + if ((!RLGL.ExtSupported.texCompDXT) && ((format == PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == PIXELFORMAT_COMPRESSED_DXT5_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!RLGL.ExtSupported.texCompETC1) && (format == PIXELFORMAT_COMPRESSED_ETC1_RGB)) + { + TRACELOG(LOG_WARNING, "GL: ETC1 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompETC2) && ((format == PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: ETC2 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == PIXELFORMAT_COMPRESSED_PVRT_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: PVRT compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompASTC) && ((format == PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) + { + TRACELOG(LOG_WARNING, "GL: ASTC compressed texture format not supported"); + return id; + } +#endif +#endif // GRAPHICS_API_OPENGL_11 + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &id); // Generate texture id + + glBindTexture(GL_TEXTURE_2D, id); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != -1) + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, (unsigned char *)data + mipOffset); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, (unsigned char *)data + mipOffset); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if (RLGL.ExtSupported.texNPOT) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if (mipmapCount > 1) + { + // Activate Trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } +#endif + + // At this point we have the texture loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + glBindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i - %i mipmaps)", id, width, height, mipmapCount); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load texture"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture/WEBGL_depth_texture extensions +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // In case depth textures not supported, we force renderbuffer usage + if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; + + // NOTE: We let the implementation to choose the best bit-depth + // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F + unsigned int glInternalFormat = GL_DEPTH_COMPONENT; + +#if defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; + else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; + else glInternalFormat = GL_DEPTH_COMPONENT16; +#endif + + if (!useRenderBuffer && RLGL.ExtSupported.texDepth) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(LOG_INFO, "TEXTURE: Depth texture loaded successfully"); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); + } +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +unsigned int rlLoadTextureCubemap(void *data, int size, int format) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int dataSize = rlGetPixelDataSize(size, size, format); + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != -1) + { + // Load cubemap faces + for (unsigned int i = 0; i < 6; i++) + { + if (data == NULL) + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if (format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) + { + // Instead of using a sized internal texture format (GL_RGB16F, GL_RGB32F), we let the driver to choose the better format for us (GL_RGB) + if (RLGL.ExtSupported.texFloat32) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); + else TRACELOG(LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + } + else if ((format == PIXELFORMAT_UNCOMPRESSED_R32) || (format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, NULL); + } + else TRACELOG(LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, (unsigned char *)data + i*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); + } + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + } + + // Set cubemap texture sampling parameters + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + if (id > 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; +} + +// Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... +void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != -1) && (format < PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, (unsigned char *)data); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); +} + +// Get OpenGL internal formats and data type from raylib PixelFormat +void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) +{ + *glInternalFormat = -1; + *glFormat = -1; + *glType = -1; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case PIXELFORMAT_UNCOMPRESSED_RGB565_BE: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5_REV; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + case PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TRACELOG(LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; + } +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + glDeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +void rlGenerateMipmaps(Texture2D *texture) +{ + glBindTexture(GL_TEXTURE_2D, texture->id); + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((texture->width > 0) && ((texture->width & (texture->width - 1)) == 0)) && + ((texture->height > 0) && ((texture->height & (texture->height - 1)) == 0))) texIsPOT = true; + +#if defined(GRAPHICS_API_OPENGL_11) + if (texIsPOT) + { + // WARNING: Manual mipmap generation only works for RGBA 32bit textures! + if (texture->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + // Retrieve texture data from VRAM + void *texData = rlReadTexturePixels(*texture); + + // NOTE: Texture data size is reallocated to fit mipmaps data + // NOTE: CPU mipmap generation only supports RGBA 32bit data + int mipmapCount = rlGenerateMipmapsData(texData, texture->width, texture->height); + + int size = texture->width*texture->height*4; + int offset = size; + + int mipWidth = texture->width/2; + int mipHeight = texture->height/2; + + // Load the mipmaps + for (int level = 1; level < mipmapCount; level++) + { + glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)texData + offset); + + size = mipWidth*mipHeight*4; + offset += size; + + mipWidth /= 2; + mipHeight /= 2; + } + + texture->mipmaps = mipmapCount + 1; + RL_FREE(texData); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data + + TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Mipmaps generated manually on CPU side, total: %i", texture->id, texture->mipmaps); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps for provided texture format", texture->id); + } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps + + #define MIN(a,b) (((a)<(b))?(a):(b)) + #define MAX(a,b) (((a)>(b))?(a):(b)) + + texture->mipmaps = 1 + (int)floor(log(MAX(texture->width, texture->height))/log(2)); + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", texture->id, texture->mipmaps); + } +#endif + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", texture->id); + + glBindTexture(GL_TEXTURE_2D, 0); +} + + +// Read texture pixel data +void *rlReadTexturePixels(Texture2D texture) +{ + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, texture.id); + + // NOTE: Using texture.id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding. + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting. + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(texture.format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(texture.width, texture.height, texture.format); + + if ((glInternalFormat != -1) && (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + pixels = RL_MALLOC(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", texture.id, texture.format); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0. There is no way to get it from texture id. + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // We are using Option 1, just need to care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(texture.width, texture.height); + + // TODO: Create depth texture/renderbuffer for fbo? + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0); + + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(texture.width, texture.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); + glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); +#endif + + return pixels; +} + + +// Read screen pixel data (color buffer) +unsigned char *rlReadScreenPixels(int width, int height) +{ + unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); + + // Flip image vertically! + unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); + + for (int y = height - 1; y >= 0; y--) + { + for (int x = 0; x < (width*4); x++) + { + imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line + + // Set alpha component value to 255 (no trasparent image retrieval) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; + } + } + + RL_FREE(screenData); + + return imgData; // NOTE: image data should be freed +} + +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(int width, int height) +{ + unsigned int fboId = 0; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer +#endif + + return fboId; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0, depthId = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_RENDERBUFFER) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer. + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update GPU buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(int bufferId, void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, bufferId); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +void rlDrawVertexArrayElements(int offset, int count, void *buffer) +{ + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short*)buffer + offset); +} + +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); +#endif +} + +void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short*)buffer + offset, instances); +#endif +} + +#if defined(GRAPHICS_API_OPENGL_11) +void rlEnableStatePointer(int vertexAttribType, void *buffer) +{ + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } +} + +void rlDisableStatePointer(int vertexAttribType) +{ + glDisableClientState(vertexAttribType); +} +#endif + +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenVertexArrays(1, &vaoId); +#endif + return vaoId; +} + +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribPointer(index, compSize, type, normalized, stride, pointer); +#endif +} + +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribDivisor(index, divisor); +#endif +} + +void rlUnloadVertexArray(unsigned int vaoId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif +} + +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- +// Load shader from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = RLGL.State.defaultVShaderId; + unsigned int fragmentShaderId = RLGL.State.defaultFShaderId; + + if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShader.id; + else + { + id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); + + if (vertexShaderId != RLGL.State.defaultVShaderId) + { + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); + } + + if (id == 0) + { + TRACELOG(LOG_WARNING, "SHADER: Failed to load custom shader code"); + id = RLGL.State.defaultShader.id; + } + } + + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256]; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } +#endif + + return id; +} + +// Compile custom shader and return shader id +unsigned int rlCompileShader(const char *shaderCode, int type) +{ + unsigned int shader = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderCode, NULL); + + GLint success = 0; + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success == GL_FALSE) + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; + //case GL_GEOMETRY_SHADER: + //case GL_COMPUTE_SHADER: + default: break; + } + + int maxLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shader, maxLength, &length, log); + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); + RL_FREE(log); + } + } + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; + //case GL_GEOMETRY_SHADER: + //case GL_COMPUTE_SHADER: + default: break; + } + } +#endif + + return shader; +} + +// Load custom shader strings and return program id +unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLint success = 0; + program = glCreateProgram(); + + glAttachShader(program, vShaderId); + glAttachShader(program, fShaderId); + + // NOTE: Default attribute shader locations must be binded before linking + glBindAttribLocation(program, 0, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(program, 1, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(program, 2, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(program, 3, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(program, 4, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(program, 5, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); +#endif + return program; +} + +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + + TRACELOG(LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shaderId, uniformName); + + if (location == -1) TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(shaderId, attribName); + + if (location == -1) TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUniformMatrix4fv(locIndex, 1, false, MatrixToFloat(mat)); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) if (RLGL.State.activeTextureId[i] == textureId) return; + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active +void rlSetShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShader.id != shader.id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShader = shader; + } +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Return internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; matrix.m1 = mat[1]; matrix.m2 = mat[2]; matrix.m3 = mat[3]; + matrix.m4 = mat[4]; matrix.m5 = mat[5]; matrix.m6 = mat[6]; matrix.m7 = mat[7]; + matrix.m8 = mat[8]; matrix.m9 = mat[9]; matrix.m10 = mat[10]; matrix.m11 = mat[11]; + matrix.m12 = mat[12]; matrix.m13 = mat[13]; matrix.m14 = mat[14]; matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Return internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; m.m1 = mat[1]; m.m2 = mat[2]; m.m3 = mat[3]; + m.m4 = mat[4]; m.m5 = mat[5]; m.m6 = mat[6]; m.m7 = mat[7]; + m.m8 = mat[8]; m.m9 = mat[9]; m.m10 = mat[10]; m.m11 = mat[11]; + m.m12 = mat[12]; m.m13 = mat[13]; m.m14 = mat[14]; m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //Matrix matStackTransform = MatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = MatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a 1x1 XY quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a 1x1 3D cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load default shader (just vertex positioning and texture coloring) +// NOTE: This shader program is used for internal buffers +// NOTE: It uses global variable: RLGL.State.defaultShader +static void rlLoadShaderDefault(void) +{ + RLGL.State.defaultShader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShader.locs[i] = -1; + + // Vertex shader directly defined, no external file required + const char *vShaderDefault = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#endif + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + const char *fShaderDefault = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + + // NOTE: Compiled vertex/fragment shaders are kept for re-use + RLGL.State.defaultVShaderId = rlCompileShader(vShaderDefault, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlCompileShader(fShaderDefault, GL_FRAGMENT_SHADER); // Compile default fragment shader + + RLGL.State.defaultShader.id = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + + if (RLGL.State.defaultShader.id > 0) + { + TRACELOG(LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShader.id); + + // Set default shader locations: attributes locations + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexPosition"); + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexTexCoord"); + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexColor"); + + // Set default shader locations: uniform locations + RLGL.State.defaultShader.locs[SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShader.id, "mvp"); + RLGL.State.defaultShader.locs[SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShader.id, "colDiffuse"); + RLGL.State.defaultShader.locs[SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShader.id, "texture0"); + } + else TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShader.id); +} + +// Unload default shader +// NOTE: It uses global variable: RLGL.State.defaultShader +static void rlUnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(RLGL.State.defaultShader.id, RLGL.State.defaultVShaderId); + glDetachShader(RLGL.State.defaultShader.id, RLGL.State.defaultFShaderId); + glDeleteShader(RLGL.State.defaultVShaderId); + glDeleteShader(RLGL.State.defaultFShaderId); + + glDeleteProgram(RLGL.State.defaultShader.id); + + RL_FREE(RLGL.State.defaultShader.locs); + + TRACELOG(LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShader.id); +} + +#if defined(SUPPORT_GL_DETAILS_INFO) +// Get compressed format official GL identifier name +static char *rlGetCompressedFormatName(int format) +{ + static char compName[64] = { 0 }; + memset(compName, 0, 64); + + switch (format) + { + // GL_EXT_texture_compression_s3tc + case 0x83F0: strcpy(compName, "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"); break; + case 0x83F1: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"); break; + case 0x83F2: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"); break; + case 0x83F3: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"); break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: strcpy(compName, "GL_COMPRESSED_RGB_FXT1_3DFX"); break; + case 0x86B1: strcpy(compName, "GL_COMPRESSED_RGBA_FXT1_3DFX"); break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: strcpy(compName, "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"); break; + case 0x8C01: strcpy(compName, "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"); break; + case 0x8C02: strcpy(compName, "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"); break; + case 0x8C03: strcpy(compName, "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"); break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: strcpy(compName, "GL_ETC1_RGB8_OES"); break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: strcpy(compName, "GL_COMPRESSED_RED_RGTC1"); break; + case 0x8DBC: strcpy(compName, "GL_COMPRESSED_SIGNED_RED_RGTC1"); break; + case 0x8DBD: strcpy(compName, "GL_COMPRESSED_RG_RGTC2"); break; + case 0x8DBE: strcpy(compName, "GL_COMPRESSED_SIGNED_RG_RGTC2"); break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: strcpy(compName, "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"); break; + case 0x8E8D: strcpy(compName, "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"); break; + case 0x8E8E: strcpy(compName, "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"); break; + case 0x8E8F: strcpy(compName, "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"); break; + // GL_ARB_ES3_compatibility + case 0x9274: strcpy(compName, "GL_COMPRESSED_RGB8_ETC2"); break; + case 0x9275: strcpy(compName, "GL_COMPRESSED_SRGB8_ETC2"); break; + case 0x9276: strcpy(compName, "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"); break; + case 0x9277: strcpy(compName, "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"); break; + case 0x9278: strcpy(compName, "GL_COMPRESSED_RGBA8_ETC2_EAC"); break; + case 0x9279: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"); break; + case 0x9270: strcpy(compName, "GL_COMPRESSED_R11_EAC"); break; + case 0x9271: strcpy(compName, "GL_COMPRESSED_SIGNED_R11_EAC"); break; + case 0x9272: strcpy(compName, "GL_COMPRESSED_RG11_EAC"); break; + case 0x9273: strcpy(compName, "GL_COMPRESSED_SIGNED_RG11_EAC"); break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"); break; + case 0x93B1: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"); break; + case 0x93B2: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"); break; + case 0x93B3: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"); break; + case 0x93B4: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"); break; + case 0x93B5: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"); break; + case 0x93B6: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"); break; + case 0x93B7: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"); break; + case 0x93B8: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"); break; + case 0x93B9: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"); break; + case 0x93BA: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"); break; + case 0x93BB: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"); break; + case 0x93BC: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"); break; + case 0x93BD: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"); break; + case 0x93D0: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"); break; + case 0x93D1: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"); break; + case 0x93D2: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"); break; + case 0x93D3: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"); break; + case 0x93D4: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"); break; + case 0x93D5: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"); break; + case 0x93D6: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"); break; + case 0x93D7: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"); break; + case 0x93D8: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"); break; + case 0x93D9: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"); break; + case 0x93DA: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"); break; + case 0x93DB: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"); break; + case 0x93DC: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"); break; + case 0x93DD: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"); break; + default: strcpy(compName, "GL_COMPRESSED_UNKNOWN"); break; + } + + return compName; +} +#endif // SUPPORT_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_11) +// Mipmaps data is generated after image data +// NOTE: Only works with RGBA (4 bytes) data! +static int rlGenerateMipmapsData(unsigned char *data, int baseWidth, int baseHeight) +{ + int mipmapCount = 1; // Required mipmap levels count (including base level) + int width = baseWidth; + int height = baseHeight; + int size = baseWidth*baseHeight*4; // Size in bytes (will include mipmaps...), RGBA only + + // Count mipmap levels required + while ((width != 1) && (height != 1)) + { + width /= 2; + height /= 2; + + TRACELOGD("TEXTURE: Next mipmap size: %i x %i", width, height); + + mipmapCount++; + + size += (width*height*4); // Add mipmap size (in bytes) + } + + TRACELOGD("TEXTURE: Total mipmaps required: %i", mipmapCount); + TRACELOGD("TEXTURE: Total size of data required: %i", size); + + unsigned char *temp = RL_REALLOC(data, size); + + if (temp != NULL) data = temp; + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to re-allocate required mipmaps memory"); + + width = baseWidth; + height = baseHeight; + size = (width*height*4); + + // Generate mipmaps + // NOTE: Every mipmap data is stored after data + Color *image = (Color *)RL_MALLOC(width*height*sizeof(Color)); + Color *mipmap = NULL; + int offset = 0; + int j = 0; + + for (int i = 0; i < size; i += 4) + { + image[j].r = data[i]; + image[j].g = data[i + 1]; + image[j].b = data[i + 2]; + image[j].a = data[i + 3]; + j++; + } + + TRACELOGD("TEXTURE: Mipmap base size (%ix%i)", width, height); + + for (int mip = 1; mip < mipmapCount; mip++) + { + mipmap = rlGenNextMipmapData(image, width, height); + + offset += (width*height*4); // Size of last mipmap + j = 0; + + width /= 2; + height /= 2; + size = (width*height*4); // Mipmap size to store after offset + + // Add mipmap to data + for (int i = 0; i < size; i += 4) + { + data[offset + i] = mipmap[j].r; + data[offset + i + 1] = mipmap[j].g; + data[offset + i + 2] = mipmap[j].b; + data[offset + i + 3] = mipmap[j].a; + j++; + } + + RL_FREE(image); + + image = mipmap; + mipmap = NULL; + } + + RL_FREE(mipmap); // free mipmap data + + return mipmapCount; +} + +// Manual mipmap generation (basic scaling algorithm) +static Color *rlGenNextMipmapData(Color *srcData, int srcWidth, int srcHeight) +{ + int x2, y2; + Color prow, pcol; + + int width = srcWidth/2; + int height = srcHeight/2; + + Color *mipmap = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + // Scaling algorithm works perfectly (box-filter) + for (int y = 0; y < height; y++) + { + y2 = 2*y; + + for (int x = 0; x < width; x++) + { + x2 = 2*x; + + prow.r = (srcData[y2*srcWidth + x2].r + srcData[y2*srcWidth + x2 + 1].r)/2; + prow.g = (srcData[y2*srcWidth + x2].g + srcData[y2*srcWidth + x2 + 1].g)/2; + prow.b = (srcData[y2*srcWidth + x2].b + srcData[y2*srcWidth + x2 + 1].b)/2; + prow.a = (srcData[y2*srcWidth + x2].a + srcData[y2*srcWidth + x2 + 1].a)/2; + + pcol.r = (srcData[(y2+1)*srcWidth + x2].r + srcData[(y2+1)*srcWidth + x2 + 1].r)/2; + pcol.g = (srcData[(y2+1)*srcWidth + x2].g + srcData[(y2+1)*srcWidth + x2 + 1].g)/2; + pcol.b = (srcData[(y2+1)*srcWidth + x2].b + srcData[(y2+1)*srcWidth + x2 + 1].b)/2; + pcol.a = (srcData[(y2+1)*srcWidth + x2].a + srcData[(y2+1)*srcWidth + x2 + 1].a)/2; + + mipmap[y*width + x].r = (prow.r + pcol.r)/2; + mipmap[y*width + x].g = (prow.g + pcol.g)/2; + mipmap[y*width + x].b = (prow.b + pcol.b)/2; + mipmap[y*width + x].a = (prow.a + pcol.a)/2; + } + } + + TRACELOGD("TEXTURE: Mipmap generated successfully (%ix%i)", width, height); + + return mipmap; +} +#endif // GRAPHICS_API_OPENGL_11 + +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +static int rlGetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} +#endif // RLGL_IMPLEMENTATION diff --git a/raylib_pi4_test/rmem.h b/raylib_pi4_test/rmem.h new file mode 100644 index 0000000..dbf417f --- /dev/null +++ b/raylib_pi4_test/rmem.h @@ -0,0 +1,739 @@ +/********************************************************************************************** +* +* rmem - raylib memory pool and objects pool +* +* A quick, efficient, and minimal free list and arena-based allocator +* +* PURPOSE: +* - A quicker, efficient memory allocator alternative to 'malloc' and friends. +* - Reduce the possibilities of memory leaks for beginner developers using Raylib. +* - Being able to flexibly range check memory if necessary. +* +* CONFIGURATION: +* +* #define RMEM_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2019 Kevin 'Assyrianic' Yonan (@assyrianic) and reviewed by Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RMEM_H +#define RMEM_H + +#include +#include + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMEMAPI __declspec(dllexport) // We are building library as a Win32 shared library (.dll) +#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMEMAPI __declspec(dllimport) // We are using library as a Win32 shared library (.dll) +#else + #define RMEMAPI // We are building or using library as a static library (or Linux shared library) +#endif + +#define RMEM_VERSION "v1.3" // changelog at bottom of header. + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Memory Pool +typedef struct MemNode MemNode; +struct MemNode { + size_t size; + MemNode *next, *prev; +}; + +// Freelist implementation +typedef struct AllocList { + MemNode *head, *tail; + size_t len; +} AllocList; + +// Arena allocator. +typedef struct Arena { + uintptr_t mem, offs; + size_t size; +} Arena; + + +enum { + MEMPOOL_BUCKET_SIZE = 8, + MEMPOOL_BUCKET_BITS = (sizeof(uintptr_t) >> 1) + 1, + MEM_SPLIT_THRESHOLD = sizeof(uintptr_t) * 4 +}; + +typedef struct MemPool { + AllocList large, buckets[MEMPOOL_BUCKET_SIZE]; + Arena arena; +} MemPool; + + +// Object Pool +typedef struct ObjPool { + uintptr_t mem, offs; + size_t objSize, freeBlocks, memSize; +} ObjPool; + + +// Double-Ended Stack aka Deque +typedef struct BiStack { + uintptr_t mem, front, back; + size_t size; +} BiStack; + + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//------------------------------------------------------------------------------------ +// Functions Declaration - Memory Pool +//------------------------------------------------------------------------------------ +RMEMAPI MemPool CreateMemPool(size_t bytes); +RMEMAPI MemPool CreateMemPoolFromBuffer(void *buf, size_t bytes); +RMEMAPI void DestroyMemPool(MemPool *mempool); + +RMEMAPI void *MemPoolAlloc(MemPool *mempool, size_t bytes); +RMEMAPI void *MemPoolRealloc(MemPool *mempool, void *ptr, size_t bytes); +RMEMAPI void MemPoolFree(MemPool *mempool, void *ptr); +RMEMAPI void MemPoolCleanUp(MemPool *mempool, void **ptrref); +RMEMAPI void MemPoolReset(MemPool *mempool); +RMEMAPI size_t GetMemPoolFreeMemory(const MemPool mempool); + +//------------------------------------------------------------------------------------ +// Functions Declaration - Object Pool +//------------------------------------------------------------------------------------ +RMEMAPI ObjPool CreateObjPool(size_t objsize, size_t len); +RMEMAPI ObjPool CreateObjPoolFromBuffer(void *buf, size_t objsize, size_t len); +RMEMAPI void DestroyObjPool(ObjPool *objpool); + +RMEMAPI void *ObjPoolAlloc(ObjPool *objpool); +RMEMAPI void ObjPoolFree(ObjPool *objpool, void *ptr); +RMEMAPI void ObjPoolCleanUp(ObjPool *objpool, void **ptrref); + +//------------------------------------------------------------------------------------ +// Functions Declaration - Double-Ended Stack +//------------------------------------------------------------------------------------ +RMEMAPI BiStack CreateBiStack(size_t len); +RMEMAPI BiStack CreateBiStackFromBuffer(void *buf, size_t len); +RMEMAPI void DestroyBiStack(BiStack *destack); + +RMEMAPI void *BiStackAllocFront(BiStack *destack, size_t len); +RMEMAPI void *BiStackAllocBack(BiStack *destack, size_t len); + +RMEMAPI void BiStackResetFront(BiStack *destack); +RMEMAPI void BiStackResetBack(BiStack *destack); +RMEMAPI void BiStackResetAll(BiStack *destack); + +RMEMAPI intptr_t BiStackMargins(BiStack destack); + +#ifdef __cplusplus +} +#endif + +#endif // RMEM_H + +/*********************************************************************************** +* +* RMEM IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RMEM_IMPLEMENTATION) + +#include // Required for: +#include // Required for: +#include // Required for: + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Make sure restrict type qualifier for pointers is defined +// NOTE: Not supported by C++, it is a C only keyword +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) || defined(_MSC_VER) + #ifndef restrict + #define restrict __restrict + #endif +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static inline size_t __AlignSize(const size_t size, const size_t align) +{ + return (size + (align - 1)) & -align; +} + +static MemNode *__SplitMemNode(MemNode *const node, const size_t bytes) +{ + uintptr_t n = ( uintptr_t )node; + MemNode *const r = ( MemNode* )(n + (node->size - bytes)); + node->size -= bytes; + r->size = bytes; + return r; +} + +static void __InsertMemNodeBefore(AllocList *const list, MemNode *const insert, MemNode *const curr) +{ + insert->next = curr; + if (curr->prev==NULL) list->head = insert; + else + { + insert->prev = curr->prev; + curr->prev->next = insert; + } + curr->prev = insert; +} + +static void __ReplaceMemNode(MemNode *const old, MemNode *const replace) +{ + replace->prev = old->prev; + replace->next = old->next; + if( old->prev != NULL ) + old->prev->next = replace; + if( old->next != NULL ) + old->next->prev = replace; +} + + +static MemNode *__RemoveMemNode(AllocList *const list, MemNode *const node) +{ + if (node->prev != NULL) node->prev->next = node->next; + else + { + list->head = node->next; + if (list->head != NULL) list->head->prev = NULL; + else list->tail = NULL; + } + + if (node->next != NULL) node->next->prev = node->prev; + else + { + list->tail = node->prev; + if (list->tail != NULL) list->tail->next = NULL; + else list->head = NULL; + } + list->len--; + return node; +} + +static MemNode *__FindMemNode(AllocList *const list, const size_t bytes) +{ + for (MemNode *node = list->head; node != NULL; node = node->next) + { + if (node->size < bytes) continue; + // close in size - reduce fragmentation by not splitting. + else if (node->size <= bytes + MEM_SPLIT_THRESHOLD) return __RemoveMemNode(list, node); + else return __SplitMemNode(node, bytes); + } + return NULL; +} + +static void __InsertMemNode(MemPool *const mempool, AllocList *const list, MemNode *const node, const bool is_bucket) +{ + if (list->head == NULL) + { + list->head = node; + list->len++; + } + else + { + for (MemNode *iter = list->head; iter != NULL; iter = iter->next) + { + if (( uintptr_t )iter == mempool->arena.offs) + { + mempool->arena.offs += iter->size; + __RemoveMemNode(list, iter); + iter = list->head; + } + const uintptr_t inode = ( uintptr_t )node; + const uintptr_t iiter = ( uintptr_t )iter; + const uintptr_t iter_end = iiter + iter->size; + const uintptr_t node_end = inode + node->size; + if (iter==node) return; + else if (iter < node) + { + // node was coalesced prior. + if (iter_end > inode) return; + else if (iter_end==inode && !is_bucket) + { + // if we can coalesce, do so. + iter->size += node->size; + return; + } + } + else if (iter > node) + { + // Address sort, lowest to highest aka ascending order. + if (iiter < node_end) return; + else if (iter==list->head && !is_bucket) + { + if (iter_end==inode) iter->size += node->size; + else if (node_end==iiter) + { + node->size += list->head->size; + node->next = list->head->next; + node->prev = NULL; + list->head = node; + } + else + { + node->next = iter; + node->prev = NULL; + iter->prev = node; + list->head = node; + list->len++; + } + return; + } + else if (iter_end==inode && !is_bucket) + { + // if we can coalesce, do so. + iter->size += node->size; + return; + } + else + { + __InsertMemNodeBefore(list, iter, node); + list->len++; + return; + } + } + } + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Memory Pool +//---------------------------------------------------------------------------------- + +MemPool CreateMemPool(const size_t size) +{ + MemPool mempool = { 0 }; + + if (size == 0) return mempool; + else + { + // Align the mempool size to at least the size of an alloc node. + uint8_t *const restrict buf = malloc(size*sizeof *buf); + if (buf==NULL) return mempool; + else + { + mempool.arena.size = size; + mempool.arena.mem = ( uintptr_t )buf; + mempool.arena.offs = mempool.arena.mem + mempool.arena.size; + return mempool; + } + } +} + +MemPool CreateMemPoolFromBuffer(void *const restrict buf, const size_t size) +{ + MemPool mempool = { 0 }; + if ((size == 0) || (buf == NULL) || (size <= sizeof(MemNode))) return mempool; + else + { + mempool.arena.size = size; + mempool.arena.mem = ( uintptr_t )buf; + mempool.arena.offs = mempool.arena.mem + mempool.arena.size; + return mempool; + } +} + +void DestroyMemPool(MemPool *const restrict mempool) +{ + if (mempool->arena.mem == 0) return; + else + { + void *const restrict ptr = ( void* )mempool->arena.mem; + free(ptr); + *mempool = (MemPool){ 0 }; + } +} + +void *MemPoolAlloc(MemPool *const mempool, const size_t size) +{ + if ((size == 0) || (size > mempool->arena.size)) return NULL; + else + { + MemNode *new_mem = NULL; + const size_t ALLOC_SIZE = __AlignSize(size + sizeof *new_mem, sizeof(intptr_t)); + const size_t BUCKET_SLOT = (ALLOC_SIZE >> MEMPOOL_BUCKET_BITS) - 1; + + // If the size is small enough, let's check if our buckets has a fitting memory block. + if (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) + { + new_mem = __FindMemNode(&mempool->buckets[BUCKET_SLOT], ALLOC_SIZE); + } + else if (mempool->large.head != NULL) + { + new_mem = __FindMemNode(&mempool->large, ALLOC_SIZE); + } + + if (new_mem == NULL) + { + // not enough memory to support the size! + if ((mempool->arena.offs - ALLOC_SIZE) < mempool->arena.mem) return NULL; + else + { + // Couldn't allocate from a freelist, allocate from available mempool. + // Subtract allocation size from the mempool. + mempool->arena.offs -= ALLOC_SIZE; + + // Use the available mempool space as the new node. + new_mem = ( MemNode* )mempool->arena.offs; + new_mem->size = ALLOC_SIZE; + } + } + + // Visual of the allocation block. + // -------------- + // | mem size | lowest addr of block + // | next node | 12 byte (32-bit) header + // | prev node | 24 byte (64-bit) header + // |------------| + // | alloc'd | + // | memory | + // | space | highest addr of block + // -------------- + new_mem->next = new_mem->prev = NULL; + uint8_t *const restrict final_mem = ( uint8_t* )new_mem + sizeof *new_mem; + return memset(final_mem, 0, new_mem->size - sizeof *new_mem); + } +} + +void *MemPoolRealloc(MemPool *const restrict mempool, void *const ptr, const size_t size) +{ + if (size > mempool->arena.size) return NULL; + // NULL ptr should make this work like regular Allocation. + else if (ptr == NULL) return MemPoolAlloc(mempool, size); + else if ((uintptr_t)ptr - sizeof(MemNode) < mempool->arena.mem) return NULL; + else + { + MemNode *const node = ( MemNode* )(( uint8_t* )ptr - sizeof *node); + const size_t NODE_SIZE = sizeof *node; + uint8_t *const resized_block = MemPoolAlloc(mempool, size); + if (resized_block == NULL) return NULL; + else + { + MemNode *const resized = ( MemNode* )(resized_block - sizeof *resized); + memmove(resized_block, ptr, (node->size > resized->size)? (resized->size - NODE_SIZE) : (node->size - NODE_SIZE)); + MemPoolFree(mempool, ptr); + return resized_block; + } + } +} + +void MemPoolFree(MemPool *const restrict mempool, void *const ptr) +{ + const uintptr_t p = ( uintptr_t )ptr; + if ((ptr == NULL) || (p - sizeof(MemNode) < mempool->arena.mem)) return; + else + { + // Behind the actual pointer data is the allocation info. + const uintptr_t block = p - sizeof(MemNode); + MemNode *const mem_node = ( MemNode* )block; + const size_t BUCKET_SLOT = (mem_node->size >> MEMPOOL_BUCKET_BITS) - 1; + + // Make sure the pointer data is valid. + if ((block < mempool->arena.offs) || + ((block - mempool->arena.mem) > mempool->arena.size) || + (mem_node->size == 0) || + (mem_node->size > mempool->arena.size)) return; + // If the mem_node is right at the arena offs, then merge it back to the arena. + else if (block == mempool->arena.offs) + { + mempool->arena.offs += mem_node->size; + } + else + { + // try to place it into bucket or large freelist. + struct AllocList *const l = (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) ? &mempool->buckets[BUCKET_SLOT] : &mempool->large; + __InsertMemNode(mempool, l, mem_node, (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE)); + } + } +} + +void MemPoolCleanUp(MemPool *const restrict mempool, void **const ptrref) +{ + if ((ptrref == NULL) || (*ptrref == NULL)) return; + else + { + MemPoolFree(mempool, *ptrref); + *ptrref = NULL; + } +} + +size_t GetMemPoolFreeMemory(const MemPool mempool) +{ + size_t total_remaining = mempool.arena.offs - mempool.arena.mem; + + for (MemNode *n=mempool.large.head; n != NULL; n = n->next) total_remaining += n->size; + + for (size_t i=0; inext) total_remaining += n->size; + + return total_remaining; +} + +void MemPoolReset(MemPool *const mempool) +{ + mempool->large.head = mempool->large.tail = NULL; + mempool->large.len = 0; + for (size_t i = 0; i < MEMPOOL_BUCKET_SIZE; i++) + { + mempool->buckets[i].head = mempool->buckets[i].tail = NULL; + mempool->buckets[i].len = 0; + } + mempool->arena.offs = mempool->arena.mem + mempool->arena.size; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Object Pool +//---------------------------------------------------------------------------------- + +ObjPool CreateObjPool(const size_t objsize, const size_t len) +{ + ObjPool objpool = { 0 }; + if ((len == 0) || (objsize == 0)) return objpool; + else + { + const size_t aligned_size = __AlignSize(objsize, sizeof(size_t)); + uint8_t *const restrict buf = calloc(len, aligned_size); + if (buf == NULL) return objpool; + objpool.objSize = aligned_size; + objpool.memSize = objpool.freeBlocks = len; + objpool.mem = ( uintptr_t )buf; + + for (size_t i=0; imem == 0) return; + else + { + void *const restrict ptr = ( void* )objpool->mem; + free(ptr); + *objpool = (ObjPool){0}; + } +} + +void *ObjPoolAlloc(ObjPool *const objpool) +{ + if (objpool->freeBlocks > 0) + { + // For first allocation, head points to the very first index. + // Head = &pool[0]; + // ret = Head == ret = &pool[0]; + size_t *const restrict block = ( size_t* )objpool->offs; + objpool->freeBlocks--; + + // after allocating, we set head to the address of the index that *Head holds. + // Head = &pool[*Head * pool.objsize]; + objpool->offs = (objpool->freeBlocks != 0)? objpool->mem + (*block*objpool->objSize) : 0; + return memset(block, 0, objpool->objSize); + } + else return NULL; +} + +void ObjPoolFree(ObjPool *const restrict objpool, void *const ptr) +{ + uintptr_t block = (uintptr_t)ptr; + if ((ptr == NULL) || (block < objpool->mem) || (block > objpool->mem + objpool->memSize*objpool->objSize)) return; + else + { + // When we free our pointer, we recycle the pointer space to store the previous index and then we push it as our new head. + // *p = index of Head in relation to the buffer; + // Head = p; + size_t *const restrict index = ( size_t* )block; + *index = (objpool->offs != 0)? (objpool->offs - objpool->mem)/objpool->objSize : objpool->memSize; + objpool->offs = block; + objpool->freeBlocks++; + } +} + +void ObjPoolCleanUp(ObjPool *const restrict objpool, void **const restrict ptrref) +{ + if (ptrref == NULL) return; + else + { + ObjPoolFree(objpool, *ptrref); + *ptrref = NULL; + } +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Double-Ended Stack +//---------------------------------------------------------------------------------- +BiStack CreateBiStack(const size_t len) +{ + BiStack destack = { 0 }; + if (len == 0) return destack; + + uint8_t *const buf = malloc(len*sizeof *buf); + if (buf==NULL) return destack; + destack.size = len; + destack.mem = ( uintptr_t )buf; + destack.front = destack.mem; + destack.back = destack.mem + len; + return destack; +} + +BiStack CreateBiStackFromBuffer(void *const buf, const size_t len) +{ + BiStack destack = { 0 }; + if (len == 0 || buf == NULL) return destack; + else + { + destack.size = len; + destack.mem = destack.front = ( uintptr_t )buf; + destack.back = destack.mem + len; + return destack; + } +} + +void DestroyBiStack(BiStack *const restrict destack) +{ + if (destack->mem == 0) return; + else + { + uint8_t *const restrict buf = ( uint8_t* )destack->mem; + free(buf); + *destack = (BiStack){0}; + } +} + +void *BiStackAllocFront(BiStack *const restrict destack, const size_t len) +{ + if (destack->mem == 0) return NULL; + else + { + const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); + // front end arena is too high! + if (destack->front + ALIGNED_LEN >= destack->back) return NULL; + else + { + uint8_t *const restrict ptr = ( uint8_t* )destack->front; + destack->front += ALIGNED_LEN; + return ptr; + } + } +} + +void *BiStackAllocBack(BiStack *const restrict destack, const size_t len) +{ + if (destack->mem == 0) return NULL; + else + { + const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); + // back end arena is too low + if (destack->back - ALIGNED_LEN <= destack->front) return NULL; + else + { + destack->back -= ALIGNED_LEN; + uint8_t *const restrict ptr = ( uint8_t* )destack->back; + return ptr; + } + } +} + +void BiStackResetFront(BiStack *const destack) +{ + if (destack->mem == 0) return; + else destack->front = destack->mem; +} + +void BiStackResetBack(BiStack *const destack) +{ + if (destack->mem == 0) return; + else destack->back = destack->mem + destack->size; +} + +void BiStackResetAll(BiStack *const destack) +{ + BiStackResetBack(destack); + BiStackResetFront(destack); +} + +inline intptr_t BiStackMargins(const BiStack destack) +{ + return destack.back - destack.front; +} + +#endif // RMEM_IMPLEMENTATION + +/******* + * Changelog + * v1.0: First Creation. + * v1.1: bug patches for the mempool and addition of object pool. + * v1.2: addition of bidirectional arena. + * v1.3: + * optimizations of allocators. + * renamed 'Stack' to 'Arena'. + * replaced certain define constants with an anonymous enum. + * refactored MemPool to no longer require active or deferred defragging. + ********/ diff --git a/raylib_pi4_test/rnet.h b/raylib_pi4_test/rnet.h new file mode 100644 index 0000000..439b105 --- /dev/null +++ b/raylib_pi4_test/rnet.h @@ -0,0 +1,2256 @@ +/********************************************************************************************** +* +* rnet - A simple and easy-to-use network module for raylib +* +* FEATURES: +* - Provides a simple and (hopefully) easy to use wrapper around the Berkeley socket API +* +* INSPIRED BY: +* SFML Sockets - https://www.sfml-dev.org/documentation/2.5.1/classsf_1_1Socket.php +* SDL_net - https://www.libsdl.org/projects/SDL_net/ +* BSD Sockets - https://www.gnu.org/software/libc/manual/html_node/Sockets.html +* BEEJ - https://beej.us/guide/bgnet/html/single/bgnet.html +* Winsock2 - https://docs.microsoft.com/en-us/windows/desktop/api/winsock2 +* +* CONTRIBUTORS: +* Jak Barnes (github: @syphonx) (Feb. 2019) - Initial version +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2019-2020 Jak Barnes (@syphonx) and Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RNET_H +#define RNET_H + +#include // Required for limits +#include // Required for platform type sizes + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Undefine any conflicting windows.h symbols +// If defined, the following flags inhibit definition of the indicated items. +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI defines and routines +#define NOKERNEL // All KERNEL defines and routines +#define NOUSER // All USER defines and routines +#define NONLS // All NLS defines and routines +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff. +#define NOHELP // Help engine interface. +#define NOPROFILER // Profiler interface. +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions +#define MMNOSOUND + +// Allow custom memory allocators +#ifndef RNET_MALLOC + #define RNET_MALLOC(sz) malloc(sz) +#endif +#ifndef RNET_CALLOC + #define RNET_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RNET_FREE + #define RNET_FREE(p) free(p) +#endif + +//---------------------------------------------------------------------------------- +// Platform type definitions +// From: https://github.com/DFHack/clsocket/blob/master/src/Host.h +//---------------------------------------------------------------------------------- + +#ifdef WIN32 +typedef int socklen_t; +#endif + +#ifndef RESULT_SUCCESS +# define RESULT_SUCCESS 0 +#endif // RESULT_SUCCESS + +#ifndef RESULT_FAILURE +# define RESULT_FAILURE 1 +#endif // RESULT_FAILURE + +#ifndef htonll +# ifdef _BIG_ENDIAN +# define htonll(x) (x) +# define ntohll(x) (x) +# else +# define htonll(x) ((((uint64) htonl(x)) << 32) + htonl(x >> 32)) +# define ntohll(x) ((((uint64) ntohl(x)) << 32) + ntohl(x >> 32)) +# endif // _BIG_ENDIAN +#endif // htonll + +//---------------------------------------------------------------------------------- +// Platform specific network includes +// From: https://github.com/SDL-mirror/SDL_net/blob/master/SDLnetsys.h +//---------------------------------------------------------------------------------- + +// Include system network headers +#if defined(_WIN32) // Windows + #define __USE_W32_SOCKETS + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + #define IPTOS_LOWDELAY 0x10 +#else // Unix + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#ifndef INVALID_SOCKET + #define INVALID_SOCKET ~(0) +#endif + +#ifndef __USE_W32_SOCKETS + #define closesocket close + #define SOCKET int + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 +#endif + +#ifdef __USE_W32_SOCKETS + #ifndef EINTR + #define EINTR WSAEINTR + #endif +#endif + +//---------------------------------------------------------------------------------- +// Module defines +//---------------------------------------------------------------------------------- + +// Network connection related defines +#define SOCKET_MAX_SET_SIZE 32 // Maximum sockets in a set +#define SOCKET_MAX_QUEUE_SIZE 16 // Maximum socket queue size +#define SOCKET_MAX_SOCK_OPTS 4 // Maximum socket options +#define SOCKET_MAX_UDPCHANNELS 32 // Maximum UDP channels +#define SOCKET_MAX_UDPADDRESSES 4 // Maximum bound UDP addresses + +// Network address related defines +#define ADDRESS_IPV4_ADDRSTRLEN 22 // IPv4 string length +#define ADDRESS_IPV6_ADDRSTRLEN 65 // IPv6 string length +#define ADDRESS_TYPE_ANY 0 // AF_UNSPEC +#define ADDRESS_TYPE_IPV4 2 // AF_INET +#define ADDRESS_TYPE_IPV6 23 // AF_INET6 +#define ADDRESS_MAXHOST 1025 // Max size of a fully-qualified domain name +#define ADDRESS_MAXSERV 32 // Max size of a service name + +// Network address related defines +#define ADDRESS_ANY (unsigned long)0x00000000 +#define ADDRESS_LOOPBACK 0x7f000001 +#define ADDRESS_BROADCAST (unsigned long)0xffffffff +#define ADDRESS_NONE 0xffffffff + +// Network resolution related defines +#define NAME_INFO_DEFAULT 0x00 // No flags set +#define NAME_INFO_NOFQDN 0x01 // Only return nodename portion for local hosts +#define NAME_INFO_NUMERICHOST 0x02 // Return numeric form of the host's address +#define NAME_INFO_NAMEREQD 0x04 // Error if the host's name not in DNS +#define NAME_INFO_NUMERICSERV 0x08 // Return numeric form of the service (port #) +#define NAME_INFO_DGRAM 0x10 // Service is a datagram service + +// Address resolution related defines +#if defined(_WIN32) + #define ADDRESS_INFO_PASSIVE (0x00000001) // Socket address will be used in bind() call + #define ADDRESS_INFO_CANONNAME (0x00000002) // Return canonical name in first ai_canonname + #define ADDRESS_INFO_NUMERICHOST (0x00000004) // Nodename must be a numeric address string + #define ADDRESS_INFO_NUMERICSERV (0x00000008) // Servicename must be a numeric port number + #define ADDRESS_INFO_DNS_ONLY (0x00000010) // Restrict queries to unicast DNS only (no LLMNR, netbios, etc.) + #define ADDRESS_INFO_ALL (0x00000100) // Query both IP6 and IP4 with AI_V4MAPPED + #define ADDRESS_INFO_ADDRCONFIG (0x00000400) // Resolution only if global address configured + #define ADDRESS_INFO_V4MAPPED (0x00000800) // On v6 failure, query v4 and convert to V4MAPPED format + #define ADDRESS_INFO_NON_AUTHORITATIVE (0x00004000) // LUP_NON_AUTHORITATIVE + #define ADDRESS_INFO_SECURE (0x00008000) // LUP_SECURE + #define ADDRESS_INFO_RETURN_PREFERRED_NAMES (0x00010000) // LUP_RETURN_PREFERRED_NAMES + #define ADDRESS_INFO_FQDN (0x00020000) // Return the FQDN in ai_canonname + #define ADDRESS_INFO_FILESERVER (0x00040000) // Resolving fileserver name resolution + #define ADDRESS_INFO_DISABLE_IDN_ENCODING (0x00080000) // Disable Internationalized Domain Names handling + #define ADDRESS_INFO_EXTENDED (0x80000000) // Indicates this is extended ADDRINFOEX(2/..) struct + #define ADDRESS_INFO_RESOLUTION_HANDLE (0x40000000) // Request resolution handle +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Boolean type +#ifdef _WIN32 + #include +#else +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum { false, true } bool; +#endif +#endif + +typedef enum { + SOCKET_TCP = 0, // SOCK_STREAM + SOCKET_UDP = 1 // SOCK_DGRAM +} SocketType; + +// Network typedefs +typedef uint32_t SocketChannel; +typedef struct _AddressInformation *AddressInformation; +typedef struct _SocketAddress *SocketAddress; +typedef struct _SocketAddressIPv4 *SocketAddressIPv4; +typedef struct _SocketAddressIPv6 *SocketAddressIPv6; +typedef struct _SocketAddressStorage *SocketAddressStorage; + +// IPAddress definition (in network byte order) +typedef struct IPAddress { + unsigned long host; // 32-bit IPv4 host address + unsigned short port; // 16-bit protocol port +} IPAddress; + +typedef struct UDPChannel { + int numbound; // The total number of addresses this channel is bound to + IPAddress address[SOCKET_MAX_UDPADDRESSES]; // The list of remote addresses this channel is bound to +} UDPChannel; + +// An option ID, value, sizeof(value) tuple for setsockopt(2). +typedef struct SocketOpt { + int id; // Socked option id + int valueLen; // Socked option value len + void *value; // Socked option value data +} SocketOpt; + +typedef struct Socket { + int ready; // Is the socket ready? i.e. has information + int status; // The last status code to have occured using this socket + bool isServer; // Is this socket a server socket (i.e. TCP/UDP Listen Server) + SocketChannel channel; // The socket handle id + SocketType type; // Is this socket a TCP or UDP socket? + + bool isIPv6; // Is this socket address an ipv6 address? + SocketAddressIPv4 addripv4; // The host/target IPv4 for this socket (in network byte order) + SocketAddressIPv6 addripv6; // The host/target IPv6 for this socket (in network byte order) + + struct UDPChannel binding[SOCKET_MAX_UDPCHANNELS]; // The amount of channels (if UDP) this socket is bound to +} Socket; + +// Configuration for a socket +typedef struct SocketConfig { + SocketType type; // The type of socket, TCP/UDP + char *host; // The host address in xxx.xxx.xxx.xxx form + char *port; // The target port/service in the form "http" or "25565" + bool server; // Listen for incoming clients? + bool nonblocking; // non-blocking operation? + int backlog_size; // set a custom backlog size + SocketOpt sockopts[SOCKET_MAX_SOCK_OPTS]; +} SocketConfig; + +typedef struct SocketDataPacket { + IPAddress address; // The source/dest address of an incoming/outgoing packet + int channel; // The src/dst channel of the packet + int maxlen; // The size of the data buffer + int status; // Packet status after sending + unsigned int len; // The length of the packet data + unsigned char *data; // The packet data +} SocketDataPacket; + +// Result from calling open with a given config +typedef struct SocketResult { + int status; // Socket result state + Socket *socket; // Socket ref +} SocketResult; + +typedef struct SocketSet { + int numsockets; // Socket set count + int maxsockets; // Socket set max + struct Socket **sockets; // Sockets array +} SocketSet; + +// Packet type +typedef struct Packet { + uint32_t size; // The total size of bytes in data + uint32_t offs; // The offset to data access + uint32_t maxs; // The max size of data + uint8_t *data; // Data stored in network byte order +} Packet; + + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +// Initialisation and cleanup +bool InitNetworkDevice(void); +void CloseNetworkDevice(void); + +// Address API +void ResolveIP(const char *ip, const char *service, int flags, char *outhost, char *outserv); +int ResolveHost(const char *address, const char *service, int addressType, int flags, AddressInformation *outAddr); +int GetAddressFamily(AddressInformation address); +int GetAddressSocketType(AddressInformation address); +int GetAddressProtocol(AddressInformation address); +char *GetAddressCanonName(AddressInformation address); +char *GetAddressHostAndPort(AddressInformation address, char *outhost, unsigned short *outport); + +// Address Memory API +AddressInformation LoadAddress(void); +void UnloadAddress(AddressInformation *addressInfo); +AddressInformation *LoadAddressList(int size); + +// Socket API +bool SocketCreate(SocketConfig *config, SocketResult *result); +bool SocketBind(SocketConfig *config, SocketResult *result); +bool SocketListen(SocketConfig *config, SocketResult *result); +bool SocketConnect(SocketConfig *config, SocketResult *result); +Socket *SocketAccept(Socket *server, SocketConfig *config); + +// General Socket API +int SocketSend(Socket *sock, const void *datap, int len); +int SocketReceive(Socket *sock, void *data, int maxlen); +SocketAddressStorage SocketGetPeerAddress(Socket *sock); +const char *GetSocketAddressHost(SocketAddressStorage storage); +short GetSocketAddressPort(SocketAddressStorage storage); +void SocketClose(Socket *sock); + +// UDP Socket API +int SocketSetChannel(Socket *socket, int channel, const IPAddress *address); +void SocketUnsetChannel(Socket *socket, int channel); + +// UDP DataPacket API +SocketDataPacket *AllocPacket(int size); +int ResizePacket(SocketDataPacket *packet, int newsize); +void FreePacket(SocketDataPacket *packet); +SocketDataPacket **AllocPacketList(int count, int size); +void FreePacketList(SocketDataPacket **packets); + +// Socket Memory API +Socket *LoadSocket(void); +void UnloadSocket(Socket **sock); +SocketResult *LoadSocketResult(void); +void UnloadSocketResult(SocketResult **result); +SocketSet *LoadSocketSet(int max); +void UnloadSocketSet(SocketSet *sockset); + +// Socket I/O API +bool IsSocketReady(Socket *sock); +bool IsSocketConnected(Socket *sock); +int AddSocket(SocketSet *set, Socket *sock); +int RemoveSocket(SocketSet *set, Socket *sock); +int CheckSockets(SocketSet *set, unsigned int timeout); + +// Packet API +void PacketSend(Packet *packet); +void PacketReceive(Packet *packet); +void PacketWrite8(Packet *packet, uint16_t value); +void PacketWrite16(Packet *packet, uint16_t value); +void PacketWrite32(Packet *packet, uint32_t value); +void PacketWrite64(Packet *packet, uint64_t value); +uint16_t PacketRead8(Packet *packet); +uint16_t PacketRead16(Packet *packet); +uint32_t PacketRead32(Packet *packet); +uint64_t PacketRead64(Packet *packet); + +#ifdef __cplusplus +} +#endif + +#endif // RNET_H + +/*********************************************************************************** +* +* RNET IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RNET_IMPLEMENTATION) + +#include // Required for: assert() +#include // Required for: FILE, fopen(), fclose(), fread() +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strncmp() + +#define NET_DEBUG_ENABLED 1 + +#if defined(SUPPORT_TRACELOG) + #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) + + #if defined(SUPPORT_TRACELOG_DEBUG) + #define TRACELOGD(...) TraceLog(LOG_DEBUG, __VA_ARGS__) + #else + #define TRACELOGD(...) (void)0 + #endif +#else + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +typedef struct _SocketAddress +{ + struct sockaddr address; +} _SocketAddress; + +typedef struct _SocketAddressIPv4 +{ + struct sockaddr_in address; +} _SocketAddressIPv4; + +typedef struct _SocketAddressIPv6 +{ + struct sockaddr_in6 address; +} _SocketAddressIPv6; + +typedef struct _SocketAddressStorage +{ + struct sockaddr_storage address; +} _SocketAddressStorage; + +typedef struct _AddressInformation +{ + struct addrinfo addr; +} _AddressInformation; + +//---------------------------------------------------------------------------------- +// Local module Functions Declarations +//---------------------------------------------------------------------------------- +static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol); +static const char *SocketAddressToString(struct sockaddr_storage *sockaddr); +static bool IsIPv4Address(const char *ip); +static bool IsIPv6Address(const char *ip); +static void *GetSocketPortPtr(struct sockaddr_storage *sa); +static void *GetSocketAddressPtr(struct sockaddr_storage *sa); +static bool IsSocketValid(Socket *sock); +static void SocketSetLastError(int err); +static int SocketGetLastError(); +static char *SocketGetLastErrorString(); +static char *SocketErrorCodeToString(int err); +static bool SocketSetDefaults(SocketConfig *config); +static bool InitSocket(Socket *sock, struct addrinfo *addr); +static bool CreateSocket(SocketConfig *config, SocketResult *outresult); +static bool SocketSetBlocking(Socket *sock); +static bool SocketSetNonBlocking(Socket *sock); +static bool SocketSetOptions(SocketConfig *config, Socket *sock); +static void SocketSetHints(SocketConfig *config, struct addrinfo *hints); + +//---------------------------------------------------------------------------------- +// Local module Functions Definition +//---------------------------------------------------------------------------------- +// Print socket information +static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol) +{ + switch (family) + { + case AF_UNSPEC: TRACELOG(LOG_DEBUG, "\tFamily: Unspecified"); break; + case AF_INET: + { + TRACELOG(LOG_DEBUG, "\tFamily: AF_INET (IPv4)"); + TRACELOG(LOG_INFO, "\t- IPv4 address %s", SocketAddressToString(addr)); + } break; + case AF_INET6: + { + TRACELOG(LOG_DEBUG, "\tFamily: AF_INET6 (IPv6)"); + TRACELOG(LOG_INFO, "\t- IPv6 address %s", SocketAddressToString(addr)); + } break; + case AF_NETBIOS: + { + TRACELOG(LOG_DEBUG, "\tFamily: AF_NETBIOS (NetBIOS)"); + } break; + default: TRACELOG(LOG_DEBUG, "\tFamily: Other %ld", family); break; + } + + TRACELOG(LOG_DEBUG, "\tSocket type:"); + switch (socktype) + { + case 0: TRACELOG(LOG_DEBUG, "\t- Unspecified"); break; + case SOCK_STREAM: TRACELOG(LOG_DEBUG, "\t- SOCK_STREAM (stream)"); break; + case SOCK_DGRAM: TRACELOG(LOG_DEBUG, "\t- SOCK_DGRAM (datagram)"); break; + case SOCK_RAW: TRACELOG(LOG_DEBUG, "\t- SOCK_RAW (raw)"); break; + case SOCK_RDM: TRACELOG(LOG_DEBUG, "\t- SOCK_RDM (reliable message datagram)"); break; + case SOCK_SEQPACKET: TRACELOG(LOG_DEBUG, "\t- SOCK_SEQPACKET (pseudo-stream packet)"); break; + default: TRACELOG(LOG_DEBUG, "\t- Other %ld", socktype); break; + } + + TRACELOG(LOG_DEBUG, "\tProtocol:"); + switch (protocol) + { + case 0: TRACELOG(LOG_DEBUG, "\t- Unspecified"); break; + case IPPROTO_TCP: TRACELOG(LOG_DEBUG, "\t- IPPROTO_TCP (TCP)"); break; + case IPPROTO_UDP: TRACELOG(LOG_DEBUG, "\t- IPPROTO_UDP (UDP)"); break; + default: TRACELOG(LOG_DEBUG, "\t- Other %ld", protocol); break; + } +} + +// Convert network ordered socket address to human readable string (127.0.0.1) +static const char *SocketAddressToString(struct sockaddr_storage *sockaddr) +{ + //static const char* ipv6[INET6_ADDRSTRLEN]; + assert(sockaddr != NULL); + assert(sockaddr->ss_family == AF_INET || sockaddr->ss_family == AF_INET6); + + switch (sockaddr->ss_family) + { + case AF_INET: + { + //struct sockaddr_in *s = ((struct sockaddr_in *)sockaddr); + //return inet_ntop(AF_INET, &s->sin_addr, ipv6, INET_ADDRSTRLEN); // TODO. + } + break; + case AF_INET6: + { + //struct sockaddr_in6 *s = ((struct sockaddr_in6 *)sockaddr); + //return inet_ntop(AF_INET6, &s->sin6_addr, ipv6, INET6_ADDRSTRLEN); // TODO. + } + break; + } + + return NULL; +} + +// Check if the null terminated string ip is a valid IPv4 address +static bool IsIPv4Address(const char *ip) +{ + /* + struct sockaddr_in sa; + int result = inet_pton(AF_INET, ip, &(sa.sin_addr)); // TODO. + return (result != 0); + */ + return false; +} + +// Check if the null terminated string ip is a valid IPv6 address +static bool IsIPv6Address(const char *ip) +{ + /* + struct sockaddr_in6 sa; + int result = inet_pton(AF_INET6, ip, &(sa.sin6_addr)); // TODO. + return result != 0; + */ + return false; +} + +// Return a pointer to the port from the correct address family (IPv4, or IPv6) +static void *GetSocketPortPtr(struct sockaddr_storage *sa) +{ + if (sa->ss_family == AF_INET) + { + return &(((struct sockaddr_in *)sa)->sin_port); + } + + return &(((struct sockaddr_in6 *)sa)->sin6_port); +} + +// Return a pointer to the address from the correct address family (IPv4, or IPv6) +static void *GetSocketAddressPtr(struct sockaddr_storage *sa) +{ + if (sa->ss_family == AF_INET) + { + return &(((struct sockaddr_in *)sa)->sin_addr); + } + + return &(((struct sockaddr_in6 *)sa)->sin6_addr); +} + +// Is the socket in a valid state? +static bool IsSocketValid(Socket *sock) +{ + if (sock != NULL) + { + return (sock->channel != INVALID_SOCKET); + } + + return false; +} + +// Sets the error code that can be retrieved through the WSAGetLastError function. +static void SocketSetLastError(int err) +{ +#if defined(_WIN32) + WSASetLastError(err); +#else + errno = err; +#endif +} + +// Returns the error status for the last Sockets operation that failed +static int SocketGetLastError(void) +{ +#if defined(_WIN32) + return WSAGetLastError(); +#else + return errno; +#endif +} + +// Returns a human-readable string representing the last error message +static char *SocketGetLastErrorString(void) +{ + return SocketErrorCodeToString(SocketGetLastError()); +} + +// Returns a human-readable string representing the error message (err) +static char *SocketErrorCodeToString(int err) +{ +#if defined(_WIN32) + static char gaiStrErrorBuffer[GAI_STRERROR_BUFFER_SIZE]; + TRACELOG(LOG_INFO, gaiStrErrorBuffer, "%s", gai_strerror(err)); + return gaiStrErrorBuffer; +#else + return gai_strerror(err); +#endif +} + +// Set the defaults in the supplied SocketConfig if they're not already set +static bool SocketSetDefaults(SocketConfig *config) +{ + if (config->backlog_size == 0) config->backlog_size = SOCKET_MAX_QUEUE_SIZE; + + return true; +} + +// Create the socket channel +static bool InitSocket(Socket *sckt, struct addrinfo *address) +{ + switch (sckt->type) + { + case SOCKET_TCP: + { + if (address->ai_family == AF_INET) sckt->channel = socket(AF_INET, SOCK_STREAM, 0); + else sckt->channel = socket(AF_INET6, SOCK_STREAM, 0); + } break; + case SOCKET_UDP: + { + if (address->ai_family == AF_INET) sckt->channel = socket(AF_INET, SOCK_DGRAM, 0); + else sckt->channel = socket(AF_INET6, SOCK_DGRAM, 0); + } break; + default: TRACELOG(LOG_WARNING, "Invalid socket type specified."); break; + } + + return IsSocketValid(sckt); +} + +// CreateSocket() - Interally called by CreateSocket() +// +// This here is the bread and butter of the socket API, This function will +// attempt to open a socket, bind and listen to it based on the config passed in +// +// SocketConfig* config - Configuration for which socket to open +// SocketResult* result - The results of this function (if any, including errors) +// +// e.g. +// SocketConfig server_config = { SocketConfig client_config = { +// .host = "127.0.0.1", .host = "127.0.0.1", +// .port = 8080, .port = 8080, +// .server = true, }; +// .nonblocking = true, +// }; +// SocketResult server_res; SocketResult client_res; +static bool CreateSocket(SocketConfig *config, SocketResult *outresult) +{ + bool success = true; + int addrstatus; + struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?) + struct addrinfo *res; // A pointer to the resulting address list + + outresult->socket->channel = INVALID_SOCKET; + outresult->status = RESULT_FAILURE; + + // Set the socket type + outresult->socket->type = config->type; + + // Set the hints based on information in the config + // + // AI_CANONNAME Causes the ai_canonname of the result to the filled out with the host's canonical (real) name. + // AI_PASSIVE: Causes the result's IP address to be filled out with INADDR_ANY (IPv4)or in6addr_any (IPv6); + // Note: This causes a subsequent call to bind() to auto-fill the IP address + // of the struct sockaddr with the address of the current host. + // + SocketSetHints(config, &hints); + + // Populate address information + addrstatus = getaddrinfo(config->host, // e.g. "www.example.com" or IP (Can be null if AI_PASSIVE flag is set + config->port, // e.g. "http" or port number + &hints, // e.g. SOCK_STREAM/SOCK_DGRAM + &res // The struct to populate + ); + + // Did we succeed? + if (addrstatus != 0) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->socket->status)); + SocketSetLastError(0); + TRACELOG(LOG_WARNING, "Failed to get resolve host %s:%s: %s", config->host, config->port, SocketGetLastErrorString()); + + return (success = false); + } + else + { + char hoststr[NI_MAXHOST]; + char portstr[NI_MAXSERV]; + //socklen_t client_len = sizeof(struct sockaddr_storage); + //int rc = getnameinfo((struct sockaddr *)res->ai_addr, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + TRACELOG(LOG_INFO, "Successfully resolved host %s:%s", hoststr, portstr); + } + + // Walk the address information linked-list + struct addrinfo *it; + for (it = res; it != NULL; it = it->ai_next) + { + // Initialise the socket + if (!InitSocket(outresult->socket, it)) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->socket->status)); + SocketSetLastError(0); + continue; + } + + // Set socket options + if (!SocketSetOptions(config, outresult->socket)) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->socket->status)); + SocketSetLastError(0); + freeaddrinfo(res); + + return (success = false); + } + } + + if (!IsSocketValid(outresult->socket)) + { + outresult->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->status)); + SocketSetLastError(0); + freeaddrinfo(res); + + return (success = false); + } + + if (success) + { + outresult->status = RESULT_SUCCESS; + outresult->socket->ready = 0; + outresult->socket->status = 0; + + if (!(config->type == SOCKET_UDP)) outresult->socket->isServer = config->server; + + switch (res->ai_addr->sa_family) + { + case AF_INET: + { + outresult->socket->addripv4 = (struct _SocketAddressIPv4 *)RNET_MALLOC(sizeof(*outresult->socket->addripv4)); + + if (outresult->socket->addripv4 != NULL) + { + memset(outresult->socket->addripv4, 0, sizeof(*outresult->socket->addripv4)); + + if (outresult->socket->addripv4 != NULL) + { + memcpy(&outresult->socket->addripv4->address, (struct sockaddr_in *)res->ai_addr, sizeof(struct sockaddr_in)); + + outresult->socket->isIPv6 = false; + char hoststr[NI_MAXHOST]; + char portstr[NI_MAXSERV]; + + socklen_t client_len = sizeof(struct sockaddr_storage); + getnameinfo((struct sockaddr *)&outresult->socket->addripv4->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + + TRACELOG(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr); + } + } + } break; + case AF_INET6: + { + outresult->socket->addripv6 = (struct _SocketAddressIPv6 *)RNET_MALLOC( + sizeof(*outresult->socket->addripv6)); + if (outresult->socket->addripv6 != NULL) + { + memset(outresult->socket->addripv6, 0, + sizeof(*outresult->socket->addripv6)); + if (outresult->socket->addripv6 != NULL) + { + memcpy(&outresult->socket->addripv6->address, + (struct sockaddr_in6 *)res->ai_addr, sizeof(struct sockaddr_in6)); + outresult->socket->isIPv6 = true; + char hoststr[NI_MAXHOST]; + char portstr[NI_MAXSERV]; + socklen_t client_len = sizeof(struct sockaddr_storage); + getnameinfo( + (struct sockaddr *)&outresult->socket->addripv6->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + TRACELOG(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr); + } + } + } break; + default: break; + } + } + + freeaddrinfo(res); + return success; +} + +// Set the state of the Socket sock to blocking +static bool SocketSetBlocking(Socket *sock) +{ + bool ret = true; +#if defined(_WIN32) + unsigned long mode = 0; + ret = ioctlsocket(sock->channel, FIONBIO, &mode); +#else + const int flags = fcntl(sock->channel, F_GETFL, 0); + if (!(flags & O_NONBLOCK)) + { + TRACELOG(LOG_DEBUG, "Socket was already in blocking mode"); + return ret; + } + + ret = (0 == fcntl(sock->channel, F_SETFL, (flags ^ O_NONBLOCK))); +#endif + return ret; +} + +// Set the state of the Socket sock to non-blocking +static bool SocketSetNonBlocking(Socket *sock) +{ + bool ret = true; +#if defined(_WIN32) + unsigned long mode = 1; + ret = ioctlsocket(sock->channel, FIONBIO, &mode); +#else + const int flags = fcntl(sock->channel, F_GETFL, 0); + + if ((flags & O_NONBLOCK)) + { + TRACELOG(LOG_DEBUG, "Socket was already in non-blocking mode"); + return ret; + } + + ret = (0 == fcntl(sock->channel, F_SETFL, (flags | O_NONBLOCK))); +#endif + return ret; +} + +// Set options specified in SocketConfig to Socket sock +static bool SocketSetOptions(SocketConfig *config, Socket *sock) +{ + for (int i = 0; i < SOCKET_MAX_SOCK_OPTS; i++) + { + SocketOpt *opt = &config->sockopts[i]; + + if (opt->id == 0) break; + + if (setsockopt(sock->channel, SOL_SOCKET, opt->id, opt->value, opt->valueLen) < 0) return false; + } + + return true; +} + +// Set "hints" in an addrinfo struct, to be passed to getaddrinfo. +static void SocketSetHints(SocketConfig *config, struct addrinfo *hints) +{ + if (config == NULL || hints == NULL) return; + + memset(hints, 0, sizeof(*hints)); + + // Check if the ip supplied in the config is a valid ipv4 ip ipv6 address + if (IsIPv4Address(config->host)) + { + hints->ai_family = AF_INET; + hints->ai_flags |= AI_NUMERICHOST; + } + else + { + if (IsIPv6Address(config->host)) + { + hints->ai_family = AF_INET6; + hints->ai_flags |= AI_NUMERICHOST; + } + else hints->ai_family = AF_UNSPEC; + } + + if (config->type == SOCKET_UDP) hints->ai_socktype = SOCK_DGRAM; + else hints->ai_socktype = SOCK_STREAM; + + + // Set passive unless UDP client + if (!(config->type == SOCKET_UDP) || config->server) hints->ai_flags = AI_PASSIVE; +} + +//---------------------------------------------------------------------------------- +// Module implementation +//---------------------------------------------------------------------------------- + +// Initialise the network (requires for windows platforms only) +bool InitNetworkDevice(void) +{ +#if defined(_WIN32) + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + int err = WSAStartup(wVersionRequested, &wsaData); + + if (err != 0) + { + TRACELOG(LOG_WARNING, "WinSock failed to initialise."); + return false; + } + else TRACELOG(LOG_INFO, "WinSock initialised."); + + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) + { + TRACELOG(LOG_WARNING, "WinSock failed to initialise."); + WSACleanup(); + return false; + } + + return true; +#else + return true; +#endif +} + +// Cleanup, and close the network +void CloseNetworkDevice(void) +{ +#if defined(_WIN32) + WSACleanup(); +#endif +} + +// Protocol-independent name resolution from an address to an ANSI host name +// and from a port number to the ANSI service name. +// +// The flags parameter can be used to customize processing of the getnameinfo function +// +// The following flags are available: +// +// NAME_INFO_DEFAULT 0x00 // No flags set +// NAME_INFO_NOFQDN 0x01 // Only return nodename portion for local hosts +// NAME_INFO_NUMERICHOST 0x02 // Return numeric form of the host's address +// NAME_INFO_NAMEREQD 0x04 // Error if the host's name not in DNS +// NAME_INFO_NUMERICSERV 0x08 // Return numeric form of the service (port #) +// NAME_INFO_DGRAM 0x10 // Service is a datagram service +void ResolveIP(const char *ip, const char *port, int flags, char *host, char *serv) +{ + // Variables + int status; // Status value to return (0) is success + struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?) + struct addrinfo *res; // A pointer to the resulting address list + + // Set the hints + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // Either IPv4 or IPv6 (AF_INET, AF_INET6) + hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP) + + // Populate address information + status = getaddrinfo(ip, // e.g. "www.example.com" or IP + port, // e.g. "http" or port number + &hints, // e.g. SOCK_STREAM/SOCK_DGRAM + &res // The struct to populate + ); + + // Did we succeed? + if (status != 0) TRACELOG(LOG_WARNING, "Failed to get resolve host %s:%s: %s", ip, port, gai_strerror(errno)); + else TRACELOG(LOG_DEBUG, "Resolving... %s::%s", ip, port); + + // Attempt to resolve network byte order ip to hostname + switch (res->ai_family) + { + case AF_INET: + { + status = getnameinfo(&*((struct sockaddr *)res->ai_addr), + sizeof(*((struct sockaddr_in *)res->ai_addr)), + host, NI_MAXHOST, serv, NI_MAXSERV, flags); + } break; + case AF_INET6: + { + /* + status = getnameinfo(&*((struct sockaddr_in6 *)res->ai_addr), // TODO. + sizeof(*((struct sockaddr_in6 *)res->ai_addr)), + host, NI_MAXHOST, serv, NI_MAXSERV, flags); + */ + } break; + default: break; + } + + if (status != 0) TRACELOG(LOG_WARNING, "Failed to resolve ip %s: %s", ip, SocketGetLastErrorString()); + else TRACELOG(LOG_DEBUG, "Successfully resolved %s::%s to %s", ip, port, host); + + // Free the pointer to the data returned by addrinfo + freeaddrinfo(res); +} + +// Protocol-independent translation from an ANSI host name to an address +// +// e.g. +// const char* address = "127.0.0.1" (local address) +// const char* port = "80" +// +// Parameters: +// const char* address - A pointer to a NULL-terminated ANSI string that contains a host (node) name or a numeric host address string. +// const char* service - A pointer to a NULL-terminated ANSI string that contains either a service name or port number represented as a string. +// +// Returns: +// The total amount of addresses found, -1 on error +// +int ResolveHost(const char *address, const char *service, int addressType, int flags, AddressInformation *outAddr) +{ + // Variables + int status; // Status value to return (0) is success + struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?) + struct addrinfo *res; // will point to the results + struct addrinfo *iterator; + assert(((address != NULL || address != 0) || (service != NULL || service != 0))); + assert(((addressType == AF_INET) || (addressType == AF_INET6) || (addressType == AF_UNSPEC))); + + // Set the hints + memset(&hints, 0, sizeof hints); + hints.ai_family = addressType; // Either IPv4 or IPv6 (ADDRESS_TYPE_IPV4, ADDRESS_TYPE_IPV6) + hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP) + hints.ai_flags = flags; + assert((hints.ai_addrlen == 0) || (hints.ai_addrlen == 0)); + assert((hints.ai_canonname == 0) || (hints.ai_canonname == 0)); + assert((hints.ai_addr == 0) || (hints.ai_addr == 0)); + assert((hints.ai_next == 0) || (hints.ai_next == 0)); + + // When the address is NULL, populate the IP for me + if (address == NULL) + { + if ((hints.ai_flags & AI_PASSIVE) == 0) hints.ai_flags |= AI_PASSIVE; + } + + TRACELOG(LOG_INFO, "Resolving host..."); + + // Populate address information + status = getaddrinfo(address, // e.g. "www.example.com" or IP + service, // e.g. "http" or port number + &hints, // e.g. SOCK_STREAM/SOCK_DGRAM + &res // The struct to populate + ); + + // Did we succeed? + if (status != 0) + { + int error = SocketGetLastError(); + SocketSetLastError(0); + TRACELOG(LOG_WARNING, "Failed to get resolve host: %s", SocketErrorCodeToString(error)); + return -1; + } + else TRACELOG(LOG_INFO, "Successfully resolved host %s:%s", address, service); + + // Calculate the size of the address information list + int size = 0; + for (iterator = res; iterator != NULL; iterator = iterator->ai_next) size++; + + // Validate the size is > 0, otherwise return + if (size <= 0) + { + TRACELOG(LOG_WARNING, "Error, no addresses found."); + return -1; + } + + // If not address list was allocated, allocate it dynamically with the known address size + if (outAddr == NULL) outAddr = (AddressInformation *)RNET_MALLOC(size * sizeof(AddressInformation)); + + // Dynamically allocate an array of address information structs + if (outAddr != NULL) + { + int i; + for (i = 0; i < size; ++i) + { + outAddr[i] = LoadAddress(); + if (outAddr[i] == NULL) + { + break; + } + } + + outAddr[i] = NULL; + if (i != size) outAddr = NULL; + } + else + { + TRACELOG(LOG_WARNING, "Error, failed to dynamically allocate memory for the address list"); + return -1; + } + + // Copy all the address information from res into outAddrList + int i = 0; + for (iterator = res; iterator != NULL; iterator = iterator->ai_next) + { + if (i < size) + { + outAddr[i]->addr.ai_flags = iterator->ai_flags; + outAddr[i]->addr.ai_family = iterator->ai_family; + outAddr[i]->addr.ai_socktype = iterator->ai_socktype; + outAddr[i]->addr.ai_protocol = iterator->ai_protocol; + outAddr[i]->addr.ai_addrlen = iterator->ai_addrlen; + *outAddr[i]->addr.ai_addr = *iterator->ai_addr; +#if NET_DEBUG_ENABLED + TRACELOG(LOG_DEBUG, "GetAddressInformation"); + TRACELOG(LOG_DEBUG, "\tFlags: 0x%x", iterator->ai_flags); + //PrintSocket(outAddr[i]->addr.ai_addr, outAddr[i]->addr.ai_family, outAddr[i]->addr.ai_socktype, outAddr[i]->addr.ai_protocol); + TRACELOG(LOG_DEBUG, "Length of this sockaddr: %d", outAddr[i]->addr.ai_addrlen); + TRACELOG(LOG_DEBUG, "Canonical name: %s", iterator->ai_canonname); +#endif + i++; + } + } + + // Free the pointer to the data returned by addrinfo + freeaddrinfo(res); + + // Return the total count of addresses found + return size; +} + +// This here is the bread and butter of the socket API, This function will +// attempt to open a socket, bind and listen to it based on the config passed in +// +// SocketConfig* config - Configuration for which socket to open +// SocketResult* result - The results of this function (if any, including errors) +// +// e.g. +// SocketConfig server_config = { SocketConfig client_config = { +// .host = "127.0.0.1", .host = "127.0.0.1", +// .port = 8080, .port = 8080, +// .server = true, }; +// .nonblocking = true, +// }; +// SocketResult server_res; SocketResult client_res; +bool SocketCreate(SocketConfig *config, SocketResult *result) +{ + // Socket creation result + bool success = true; + + // Make sure we've not received a null config or result pointer + if (config == NULL || result == NULL) return (success = false); + + // Set the defaults based on the config + if (!SocketSetDefaults(config)) + { + TRACELOG(LOG_WARNING, "Configuration Error."); + success = false; + } + else + { + // Create the socket + if (CreateSocket(config, result)) + { + if (config->nonblocking) SocketSetNonBlocking(result->socket); + else SocketSetBlocking(result->socket); + } + else success = false; + } + + return success; +} + +// Bind a socket to a local address +// Note: The bind function is required on an unconnected socket before subsequent calls to the listen function. +bool SocketBind(SocketConfig *config, SocketResult *result) +{ + bool success = false; + result->status = RESULT_FAILURE; + struct sockaddr_storage *sock_addr = NULL; + + // Don't bind to a socket that isn't configured as a server + if (!IsSocketValid(result->socket) || !config->server) + { + TRACELOG(LOG_WARNING, Cannot bind to socket marked as \"Client\" in SocketConfig."); + success = false; + } + else + { + if (result->socket->isIPv6) sock_addr = (struct sockaddr_storage *)&result->socket->addripv6->address; + else sock_addr = (struct sockaddr_storage *)&result->socket->addripv4->address; + + if (sock_addr != NULL) + { + if (bind(result->socket->channel, (struct sockaddr *)sock_addr, sizeof(*sock_addr)) != SOCKET_ERROR) + { + TRACELOG(LOG_INFO, "Successfully bound socket."); + success = true; + } + else + { + result->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + SocketSetLastError(0); + success = false; + } + } + } + + // Was the bind a success? + if (success) + { + result->status = RESULT_SUCCESS; + result->socket->ready = 0; + result->socket->status = 0; + socklen_t sock_len = sizeof(*sock_addr); + + if (getsockname(result->socket->channel, (struct sockaddr *)sock_addr, &sock_len) < 0) + { + TRACELOG(LOG_WARNING, "Couldn't get socket address"); + } + else + { + struct sockaddr_in *s = (struct sockaddr_in *)sock_addr; + // result->socket->address.host = s->sin_addr.s_addr; + // result->socket->address.port = s->sin_port; + + result->socket->addripv4 = (struct _SocketAddressIPv4 *)RNET_MALLOC(sizeof(*result->socket->addripv4)); + + if (result->socket->addripv4 != NULL) memset(result->socket->addripv4, 0, sizeof(*result->socket->addripv4)); + + memcpy(&result->socket->addripv4->address, (struct sockaddr_in *)&s->sin_addr, sizeof(struct sockaddr_in)); + } + } + return success; +} + +// Listens (and queues) incoming connections requests for a bound port. +bool SocketListen(SocketConfig *config, SocketResult *result) +{ + bool success = false; + result->status = RESULT_FAILURE; + + // Don't bind to a socket that isn't configured as a server + if (!IsSocketValid(result->socket) || !config->server) + { + TRACELOG(LOG_WARNING, "Cannot listen on socket marked as \"Client\" in SocketConfig."); + success = false; + } + else + { + // Don't listen on UDP sockets + if (!(config->type == SOCKET_UDP)) + { + if (listen(result->socket->channel, config->backlog_size) != SOCKET_ERROR) + { + TRACELOG(LOG_INFO, "Started listening on socket..."); + success = true; + } + else + { + success = false; + result->socket->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + SocketSetLastError(0); + } + } + else + { + TRACELOG(LOG_WARNING, "Cannot listen on socket marked as \"UDP\" (datagram) in SocketConfig."); + success = false; + } + } + + // Was the listen a success? + if (success) + { + result->status = RESULT_SUCCESS; + result->socket->ready = 0; + result->socket->status = 0; + } + + return success; +} + +// Connect the socket to the destination specified by "host" and "port" in SocketConfig +bool SocketConnect(SocketConfig *config, SocketResult *result) +{ + bool success = true; + result->status = RESULT_FAILURE; + + // Only bind to sockets marked as server + if (config->server) + { + TRACELOG(LOG_WARNING, "Cannot connect to socket marked as \"Server\" in SocketConfig."); + success = false; + } + else + { + if (IsIPv4Address(config->host)) + { + struct sockaddr_in ip4addr; + ip4addr.sin_family = AF_INET; + unsigned long hport; + hport = strtoul(config->port, NULL, 0); + ip4addr.sin_port = (unsigned short)(hport); + + // TODO: Changed the code to avoid the usage of inet_pton and inet_ntop replacing them with getnameinfo (that should have a better support on windows). + + //inet_pton(AF_INET, config->host, &ip4addr.sin_addr); + int connect_result = connect(result->socket->channel, (struct sockaddr *)&ip4addr, sizeof(ip4addr)); + + if (connect_result == SOCKET_ERROR) + { + result->socket->status = SocketGetLastError(); + SocketSetLastError(0); + + switch (result->socket->status) + { + case WSAEWOULDBLOCK: success = true; break; + default: + { + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + success = false; + } break; + } + } + else + { + TRACELOG(LOG_INFO, "Successfully connected to socket."); + success = true; + } + } + else + { + if (IsIPv6Address(config->host)) + { + struct sockaddr_in6 ip6addr; + ip6addr.sin6_family = AF_INET6; + unsigned long hport; + hport = strtoul(config->port, NULL, 0); + ip6addr.sin6_port = htons((unsigned short)hport); + //inet_pton(AF_INET6, config->host, &ip6addr.sin6_addr); // TODO. + int connect_result = connect(result->socket->channel, (struct sockaddr *)&ip6addr, sizeof(ip6addr)); + + if (connect_result == SOCKET_ERROR) + { + result->socket->status = SocketGetLastError(); + SocketSetLastError(0); + + switch (result->socket->status) + { + case WSAEWOULDBLOCK: success = true; break; + default: + { + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(result->socket->status)); + success = false; + } break; + } + } + else + { + TRACELOG(LOG_INFO, "Successfully connected to socket."); + success = true; + } + } + } + } + + if (success) + { + result->status = RESULT_SUCCESS; + result->socket->ready = 0; + result->socket->status = 0; + } + + return success; +} + +// Closes an existing socket +// +// SocketChannel socket - The id of the socket to close +void SocketClose(Socket *sock) +{ + if (sock != NULL) + { + if (sock->channel != INVALID_SOCKET) closesocket(sock->channel); + } +} + +// Returns the sockaddress for a specific socket in a generic storage struct +SocketAddressStorage SocketGetPeerAddress(Socket *sock) +{ + // TODO. + /* + if (sock->isServer) return NULL; + if (sock->isIPv6) return sock->addripv6; + else return sock->addripv4; + */ + + return NULL; +} + +// Return the address-type appropriate host portion of a socket address +const char *GetSocketAddressHost(SocketAddressStorage storage) +{ + assert(storage->address.ss_family == AF_INET || storage->address.ss_family == AF_INET6); + return SocketAddressToString((struct sockaddr_storage *)storage); +} + +// Return the address-type appropriate port(service) portion of a socket address +short GetSocketAddressPort(SocketAddressStorage storage) +{ + //return ntohs(GetSocketPortPtr(storage)); // TODO. + + return 0; +} + +// The accept function permits an incoming connection attempt on a socket. +// +// SocketChannel listener - The socket to listen for incoming connections on (i.e. server) +// SocketResult* out - The result of this function (if any, including errors) +// +// e.g. +// +// SocketResult connection; +// bool connected = false; +// if (!connected) +// { +// if (SocketAccept(server_res.socket.channel, &connection)) +// { +// connected = true; +// } +// } +Socket *SocketAccept(Socket *server, SocketConfig *config) +{ + if (!server->isServer || server->type == SOCKET_UDP) return NULL; + + struct sockaddr_storage sock_addr; + socklen_t sock_alen; + Socket *sock = LoadSocket(); + server->ready = 0; + sock_alen = sizeof(sock_addr); + sock->channel = accept(server->channel, (struct sockaddr *)&sock_addr, &sock_alen); + + if (sock->channel == INVALID_SOCKET) + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status)); + SocketSetLastError(0); + SocketClose(sock); + + return NULL; + } + + (config->nonblocking) ? SocketSetNonBlocking(sock) : SocketSetBlocking(sock); + sock->isServer = false; + sock->ready = 0; + sock->type = server->type; + + switch (sock_addr.ss_family) + { + case AF_INET: + { + struct sockaddr_in *s = ((struct sockaddr_in *)&sock_addr); + sock->addripv4 = (struct _SocketAddressIPv4 *)RNET_MALLOC(sizeof(*sock->addripv4)); + + if (sock->addripv4 != NULL) + { + memset(sock->addripv4, 0, sizeof(*sock->addripv4)); + memcpy(&sock->addripv4->address, (struct sockaddr_in *)&s->sin_addr, sizeof(struct sockaddr_in)); + TRACELOG(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *)s), ntohs(sock->addripv4->address.sin_port)); + } + } break; + case AF_INET6: + { + struct sockaddr_in6 *s = ((struct sockaddr_in6 *)&sock_addr); + sock->addripv6 = (struct _SocketAddressIPv6 *)RNET_MALLOC(sizeof(*sock->addripv6)); + + if (sock->addripv6 != NULL) + { + memset(sock->addripv6, 0, sizeof(*sock->addripv6)); + memcpy(&sock->addripv6->address, (struct sockaddr_in6 *)&s->sin6_addr, sizeof(struct sockaddr_in6)); + TRACELOG(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *)s), ntohs(sock->addripv6->address.sin6_port)); + } + } break; + } + + return sock; +} + +// Verify that the channel is in the valid range +static int ValidChannel(int channel) +{ + if ((channel < 0) || (channel >= SOCKET_MAX_UDPCHANNELS)) + { + TRACELOG(LOG_WARNING, "Invalid channel"); + return 0; + } + + return 1; +} + +// Set the socket channel +int SocketSetChannel(Socket *socket, int channel, const IPAddress *address) +{ + struct UDPChannel *binding; + + if (socket == NULL) + { + TRACELOG(LOG_WARNING, "Passed a NULL socket"); + return (-1); + } + + if (channel == -1) + { + for (channel = 0; channel < SOCKET_MAX_UDPCHANNELS; ++channel) + { + binding = &socket->binding[channel]; + if (binding->numbound < SOCKET_MAX_UDPADDRESSES) break; + } + } + else + { + if (!ValidChannel(channel)) return (-1); + + binding = &socket->binding[channel]; + } + + if (binding->numbound == SOCKET_MAX_UDPADDRESSES) + { + TRACELOG(LOG_WARNING, "No room for new addresses"); + return (-1); + } + + binding->address[binding->numbound++] = *address; + + return (channel); +} + +// Remove the socket channel +void SocketUnsetChannel(Socket *socket, int channel) +{ + if ((channel >= 0) && (channel < SOCKET_MAX_UDPCHANNELS)) socket->binding[channel].numbound = 0; +} + +/* Allocate/free a single UDP packet 'size' bytes long. + The new packet is returned, or NULL if the function ran out of memory. + */ +SocketDataPacket *AllocPacket(int size) +{ + SocketDataPacket *packet = (SocketDataPacket *)RNET_MALLOC(sizeof(*packet)); + int error = 1; + + if (packet != NULL) + { + packet->maxlen = size; + packet->data = (uint8_t *)RNET_MALLOC(size); + if (packet->data != NULL) + { + error = 0; + } + } + + if (error) + { + FreePacket(packet); + packet = NULL; + } + + return (packet); +} + +int ResizePacket(SocketDataPacket *packet, int newsize) +{ + uint8_t *newdata = (uint8_t *)RNET_MALLOC(newsize); + + if (newdata != NULL) + { + RNET_FREE(packet->data); + packet->data = newdata; + packet->maxlen = newsize; + } + + return (packet->maxlen); +} + +void FreePacket(SocketDataPacket *packet) +{ + if (packet) + { + RNET_FREE(packet->data); + RNET_FREE(packet); + } +} + +// Allocate/Free a UDP packet vector (array of packets) of 'howmany' packets, each 'size' bytes long. +// A pointer to the packet array is returned, or NULL if the function ran out of memory. +SocketDataPacket **AllocPacketList(int howmany, int size) +{ + SocketDataPacket **packetV = (SocketDataPacket **)RNET_MALLOC((howmany + 1) * sizeof(*packetV)); + + if (packetV != NULL) + { + int i; + for (i = 0; i < howmany; ++i) + { + packetV[i] = AllocPacket(size); + if (packetV[i] == NULL) + { + break; + } + } + packetV[i] = NULL; + + if (i != howmany) + { + FreePacketList(packetV); + packetV = NULL; + } + } + + return (packetV); +} + +void FreePacketList(SocketDataPacket **packetV) +{ + if (packetV) + { + for (int i = 0; packetV[i]; ++i) FreePacket(packetV[i]); + RNET_FREE(packetV); + } +} + +// Send 'len' bytes of 'data' over the non-server socket 'sock' +int SocketSend(Socket *sock, const void *datap, int length) +{ + int sent = 0; + int left = length; + int status = -1; + int numsent = 0; + const unsigned char *data = (const unsigned char *)datap; + + // Server sockets are for accepting connections only + if (sock->isServer) + { + TRACELOG(LOG_WARNING, "Cannot send information on a server socket"); + return -1; + } + + // Which socket are we trying to send data on + switch (sock->type) + { + case SOCKET_TCP: + { + SocketSetLastError(0); + do + { + length = send(sock->channel, (const char *)data, left, 0); + if (length > 0) + { + sent += length; + left -= length; + data += length; + } + } while ((left > 0) && // While we still have bytes left to send + ((length > 0) || // The amount of bytes we actually sent is > 0 + (SocketGetLastError() == WSAEINTR)) // The socket was interupted + ); + + if (length == SOCKET_ERROR) + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_DEBUG, "Socket Error: %s", SocketErrorCodeToString(sock->status)); + SocketSetLastError(0); + } + else TRACELOG(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, sent); + + return sent; + } break; + case SOCKET_UDP: + { + SocketSetLastError(0); + + if (sock->isIPv6) status = sendto(sock->channel, (const char *)data, left, 0, (struct sockaddr *)&sock->addripv6->address, sizeof(sock->addripv6->address)); + else status = sendto(sock->channel, (const char *)data, left, 0, (struct sockaddr *)&sock->addripv4->address, sizeof(sock->addripv4->address)); + + if (sent >= 0) + { + sock->status = 0; + ++numsent; + TRACELOG(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, status); + } + else + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_DEBUG, "Socket Error: %s", SocketGetLastErrorString(sock->status)); + SocketSetLastError(0); + return 0; + } + + return numsent; + } break; + default: break; + } + + return -1; +} + +// Receive up to 'maxlen' bytes of data over the non-server socket 'sock', +// and store them in the buffer pointed to by 'data'. +// This function returns the actual amount of data received. If the return +// value is less than or equal to zero, then either the remote connection was +// closed, or an unknown socket error occurred. +int SocketReceive(Socket *sock, void *data, int maxlen) +{ + int len = 0; + int numrecv = 0; + int status = 0; + socklen_t sock_len; + struct sockaddr_storage sock_addr; + //char ip[INET6_ADDRSTRLEN]; + + // Server sockets are for accepting connections only + if (sock->isServer && (sock->type == SOCKET_TCP)) + { + sock->status = SocketGetLastError(); + TRACELOG(LOG_DEBUG, "Socket Error: %s", "Server sockets cannot be used to receive data"); + SocketSetLastError(0); + return 0; + } + + // Which socket are we trying to send data on + switch (sock->type) + { + case SOCKET_TCP: + { + SocketSetLastError(0); + do + { + len = recv(sock->channel, (char *)data, maxlen, 0); + } while (SocketGetLastError() == WSAEINTR); + + if (len > 0) + { + // Who sent the packet? + if (sock->type == SOCKET_UDP) + { + //TRACELOG(LOG_DEBUG, "Received data from: %s", inet_ntop(sock_addr.ss_family, GetSocketAddressPtr((struct sockaddr *)&sock_addr), ip, sizeof(ip))); + } + + ((unsigned char *)data)[len] = '\0'; // Add null terminating character to the end of the stream + TRACELOG(LOG_DEBUG, "Received \"%s\" (%d bytes)", data, len); + } + + sock->ready = 0; + return len; + } break; + case SOCKET_UDP: + { + SocketSetLastError(0); + sock_len = sizeof(sock_addr); + status = recvfrom(sock->channel, // The receving channel + data, // A pointer to the data buffer to fill + maxlen, // The max length of the data to fill + 0, // Flags + (struct sockaddr *)&sock_addr, // The address of the recevied data + &sock_len // The length of the received data address + ); + + if (status >= 0) ++numrecv; + else + { + sock->status = SocketGetLastError(); + + switch (sock->status) + { + case WSAEWOULDBLOCK: break; + default: TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status)); break; + } + + SocketSetLastError(0); + return 0; + } + + sock->ready = 0; + return numrecv; + } break; + default: break; + } + + return -1; +} + +// Does the socket have it's 'ready' flag set? +bool IsSocketReady(Socket *sock) +{ + return (sock != NULL) && (sock->ready); +} + +// Check if the socket is considered connected +bool IsSocketConnected(Socket *sock) +{ +#if defined(_WIN32) + FD_SET writefds; + FD_ZERO(&writefds); + FD_SET(sock->channel, &writefds); + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 1000000000UL; + int total = select(0, NULL, &writefds, NULL, &timeout); + + if (total == -1) + { // Error + sock->status = SocketGetLastError(); + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status)); + SocketSetLastError(0); + } + else if (total == 0) return false; // Timeout + else if (FD_ISSET(sock->channel, &writefds)) return true; + + return false; +#else + return true; +#endif +} + +// Allocate and return a SocketResult struct +SocketResult *LoadSocketResult(void) +{ + struct SocketResult *res = (struct SocketResult *)RNET_MALLOC(sizeof(*res)); + + if (res != NULL) + { + memset(res, 0, sizeof(*res)); + if ((res->socket = LoadSocket()) == NULL) + { + RNET_FREE(res); + res = NULL; + } + } + + return res; +} + +// Free an allocated SocketResult +void UnloadSocketResult(SocketResult **result) +{ + if (*result != NULL) + { + if ((*result)->socket != NULL) UnloadSocket(&((*result)->socket)); + + RNET_FREE(*result); + *result = NULL; + } +} + +// Allocate a Socket +Socket *LoadSocket(void) +{ + struct Socket *sock; + sock = (Socket *)RNET_MALLOC(sizeof(*sock)); + + if (sock != NULL) memset(sock, 0, sizeof(*sock)); + else + { + TRACELOG(LOG_WARNING, "Ran out of memory attempting to allocate a socket"); + SocketClose(sock); + RNET_FREE(sock); + sock = NULL; + } + + return sock; +} + +// Free an allocated Socket +void UnloadSocket(Socket **sock) +{ + if (*sock != NULL) + { + RNET_FREE(*sock); + *sock = NULL; + } +} + +// Allocate a SocketSet +SocketSet *LoadSocketSet(int max) +{ + struct SocketSet *set = (struct SocketSet *)RNET_MALLOC(sizeof(*set)); + + if (set != NULL) + { + set->numsockets = 0; + set->maxsockets = max; + set->sockets = (struct Socket **)RNET_MALLOC(max * sizeof(*set->sockets)); + if (set->sockets != NULL) + { + for (int i = 0; i < max; ++i) set->sockets[i] = NULL; + } + else + { + RNET_FREE(set); + set = NULL; + } + } + + return (set); +} + +// Free an allocated SocketSet +void UnloadSocketSet(SocketSet *set) +{ + if (set) + { + RNET_FREE(set->sockets); + RNET_FREE(set); + } +} + +// Add a Socket "sock" to the SocketSet "set" +int AddSocket(SocketSet *set, Socket *sock) +{ + if (sock != NULL) + { + if (set->numsockets == set->maxsockets) + { + TRACELOG(LOG_DEBUG, "Socket Error: %s", "SocketSet is full"); + SocketSetLastError(0); + return (-1); + } + set->sockets[set->numsockets++] = (struct Socket *)sock; + } + else + { + TRACELOG(LOG_DEBUG, "Socket Error: %s", "Socket was null"); + SocketSetLastError(0); + return (-1); + } + + return (set->numsockets); +} + +// Remove a Socket "sock" to the SocketSet "set" +int RemoveSocket(SocketSet *set, Socket *sock) +{ + if (sock != NULL) + { + int i = 0; + for (i = 0; i < set->numsockets; ++i) + { + if (set->sockets[i] == (struct Socket *)sock) break; + } + + if (i == set->numsockets) + { + TRACELOG(LOG_DEBUG, "Socket Error: %s", "Socket not found"); + SocketSetLastError(0); + return (-1); + } + + --set->numsockets; + for (; i < set->numsockets; ++i) set->sockets[i] = set->sockets[i + 1]; + } + + return (set->numsockets); +} + +// Check the sockets in the socket set for pending information +int CheckSockets(SocketSet *set, unsigned int timeout) +{ + int i; + SOCKET maxfd; + int retval; + struct timeval tv; + fd_set mask; + + /* Find the largest file descriptor */ + maxfd = 0; + for (i = set->numsockets - 1; i >= 0; --i) + { + if (set->sockets[i]->channel > maxfd) + { + maxfd = set->sockets[i]->channel; + } + } + + // Check the file descriptors for available data + do + { + SocketSetLastError(0); + + // Set up the mask of file descriptors + FD_ZERO(&mask); + for (i = set->numsockets - 1; i >= 0; --i) + { + FD_SET(set->sockets[i]->channel, &mask); + } // Set up the timeout + + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + /* Look! */ + retval = select(maxfd + 1, &mask, NULL, NULL, &tv); + } while (SocketGetLastError() == WSAEINTR); + + // Mark all file descriptors ready that have data available + if (retval > 0) + { + for (i = set->numsockets - 1; i >= 0; --i) + { + if (FD_ISSET(set->sockets[i]->channel, &mask)) set->sockets[i]->ready = 1; + } + } + + return retval; +} + +// Allocate an AddressInformation +AddressInformation LoadAddress(void) +{ + AddressInformation addressInfo = NULL; + addressInfo = (AddressInformation)RNET_CALLOC(1, sizeof(*addressInfo)); + + if (addressInfo != NULL) + { + addressInfo->addr.ai_addr = (struct sockaddr *)RNET_CALLOC(1, sizeof(struct sockaddr)); + if (addressInfo->addr.ai_addr == NULL) TRACELOG(LOG_WARNING, "Failed to allocate memory for \"struct sockaddr\""); + } + else TRACELOG(LOG_WARNING, "Failed to allocate memory for \"struct AddressInformation\""); + + return addressInfo; +} + +// Free an AddressInformation struct +void UnloadAddress(AddressInformation *addressInfo) +{ + if (*addressInfo != NULL) + { + if ((*addressInfo)->addr.ai_addr != NULL) + { + RNET_FREE((*addressInfo)->addr.ai_addr); + (*addressInfo)->addr.ai_addr = NULL; + } + + RNET_FREE(*addressInfo); + *addressInfo = NULL; + } +} + +// Allocate a list of AddressInformation +AddressInformation *LoadAddressList(int size) +{ + AddressInformation *addr; + addr = (AddressInformation *)RNET_MALLOC(size * sizeof(AddressInformation)); + return addr; +} + +// Opaque datatype accessor addrinfo->ai_family +int GetAddressFamily(AddressInformation address) +{ + return address->addr.ai_family; +} + +// Opaque datatype accessor addrinfo->ai_socktype +int GetAddressSocketType(AddressInformation address) +{ + return address->addr.ai_socktype; +} + +// Opaque datatype accessor addrinfo->ai_protocol +int GetAddressProtocol(AddressInformation address) +{ + return address->addr.ai_protocol; +} + +// Opaque datatype accessor addrinfo->ai_canonname +char *GetAddressCanonName(AddressInformation address) +{ + return address->addr.ai_canonname; +} + +// Opaque datatype accessor addrinfo->ai_addr +char *GetAddressHostAndPort(AddressInformation address, char *outhost, unsigned short *outport) +{ + //char *ip[INET6_ADDRSTRLEN]; + char *result = NULL; + struct sockaddr_storage *storage = (struct sockaddr_storage *)address->addr.ai_addr; + + switch (storage->ss_family) + { + case AF_INET: + { + struct sockaddr_in *s = ((struct sockaddr_in *)address->addr.ai_addr); + //result = inet_ntop(AF_INET, &s->sin_addr, ip, INET_ADDRSTRLEN); // TODO. + *outport = ntohs(s->sin_port); + } break; + case AF_INET6: + { + struct sockaddr_in6 *s = ((struct sockaddr_in6 *)address->addr.ai_addr); + //result = inet_ntop(AF_INET6, &s->sin6_addr, ip, INET6_ADDRSTRLEN); // TODO. + *outport = ntohs(s->sin6_port); + } break; + default: break; + } + + if (result == NULL) + { + TRACELOG(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(SocketGetLastError())); + SocketSetLastError(0); + } + else + { + strcpy(outhost, result); + } + return result; +} + +// +void PacketSend(Packet *packet) +{ + TRACELOG(LOG_INFO, "Sending packet (%s) with size %d\n", packet->data, packet->size); +} + +// +void PacketReceive(Packet *packet) +{ + TRACELOG(LOG_INFO, "Receiving packet (%s) with size %d\n", packet->data, packet->size); +} + +// +void PacketWrite16(Packet *packet, uint16_t value) +{ + TRACELOG(LOG_INFO, "Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value); + uint8_t *data = packet->data + packet->offs; + *data++ = (uint8_t)(value >> 8); + *data++ = (uint8_t)(value); + packet->size += sizeof(uint16_t); + packet->offs += sizeof(uint16_t); + TRACELOG(LOG_INFO, "Network: 0x%04" PRIX16 " - %" PRIu16 "\n", (uint16_t) *data, (uint16_t) *data); +} + +// +void PacketWrite32(Packet *packet, uint32_t value) +{ + TRACELOG(LOG_INFO, "Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value); + uint8_t *data = packet->data + packet->offs; + *data++ = (uint8_t)(value >> 24); + *data++ = (uint8_t)(value >> 16); + *data++ = (uint8_t)(value >> 8); + *data++ = (uint8_t)(value); + packet->size += sizeof(uint32_t); + packet->offs += sizeof(uint32_t); + + TRACELOG(LOG_INFO, "Network: 0x%08" PRIX32 " - %" PRIu32 "\n", + (uint32_t)(((intptr_t) packet->data) - packet->offs), + (uint32_t)(((intptr_t) packet->data) - packet->offs)); +} + +// +void PacketWrite64(Packet *packet, uint64_t value) +{ + TRACELOG(LOG_INFO, "Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value); + + uint8_t *data = packet->data + packet->offs; + *data++ = (uint8_t)(value >> 56); + *data++ = (uint8_t)(value >> 48); + *data++ = (uint8_t)(value >> 40); + *data++ = (uint8_t)(value >> 32); + *data++ = (uint8_t)(value >> 24); + *data++ = (uint8_t)(value >> 16); + *data++ = (uint8_t)(value >> 8); + *data++ = (uint8_t)(value); + packet->size += sizeof(uint64_t); + packet->offs += sizeof(uint64_t); + + TRACELOG(LOG_INFO, "Network: 0x%016" PRIX64 " - %" PRIu64 "\n", (uint64_t)(packet->data - packet->offs), (uint64_t)(packet->data - packet->offs)); +} + +// +uint16_t PacketRead16(Packet *packet) +{ + uint8_t *data = packet->data + packet->offs; + packet->size += sizeof(uint16_t); + packet->offs += sizeof(uint16_t); + uint16_t value = ((uint16_t) data[0] << 8) | data[1]; + TRACELOG(LOG_INFO, "Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value); + + return value; +} + +// +uint32_t PacketRead32(Packet *packet) +{ + uint8_t *data = packet->data + packet->offs; + packet->size += sizeof(uint32_t); + packet->offs += sizeof(uint32_t); + uint32_t value = ((uint32_t) data[0] << 24) | ((uint32_t) data[1] << 16) | ((uint32_t) data[2] << 8) | data[3]; + TRACELOG(LOG_INFO, "Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value); + + return value; +} + +// +uint64_t PacketRead64(Packet *packet) +{ + uint8_t *data = packet->data + packet->offs; + packet->size += sizeof(uint64_t); + packet->offs += sizeof(uint64_t); + uint64_t value = ((uint64_t) data[0] << 56) | ((uint64_t) data[1] << 48) | ((uint64_t) data[2] << 40) | ((uint64_t) data[3] << 32) | ((uint64_t) data[4] << 24) | ((uint64_t) data[5] << 16) | ((uint64_t) data[6] << 8) | data[7]; + TRACELOG(LOG_INFO, "Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value); + + return value; +} + +#endif // RNET_IMPLEMENTATION \ No newline at end of file diff --git a/raylib_pi4_test/shapes.c b/raylib_pi4_test/shapes.c new file mode 100644 index 0000000..ae26111 --- /dev/null +++ b/raylib_pi4_test/shapes.c @@ -0,0 +1,1662 @@ +/********************************************************************************************** +* +* raylib.shapes - Basic functions to draw 2d Shapes and check collisions +* +* CONFIGURATION: +* +* #define SUPPORT_QUADS_DRAW_MODE +* Use QUADS instead of TRIANGLES for drawing when possible. +* Some lines-based shapes could still use lines +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 + +#include // Required for: sinf(), asinf(), cosf(), acosf(), sqrtf(), fabsf() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Error rate to calculate how many segments we need to draw a smooth circle, +// taken from https://stackoverflow.com/a/2244088 +#ifndef SMOOTH_CIRCLE_ERROR_RATE + #define SMOOTH_CIRCLE_ERROR_RATE 0.5f +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Not here... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (usually a white pixel) +Rectangle texShapesRec = { 0, 0, 1, 1 }; // Texture source rectangle used on shapes drawing + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static float EaseCubicInOut(float t, float b, float c, float d); // Cubic easing + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +void SetShapesTexture(Texture2D texture, Rectangle source) +{ + texShapes = texture; + texShapesRec = source; +} + +// Draw a pixel +void DrawPixel(int posX, int posY, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2i(posX, posY); + rlVertex2i(posX + 1, posY + 1); + rlEnd(); +} + +// Draw a pixel (Vector version) +void DrawPixelV(Vector2 position, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(position.x, position.y); + rlVertex2f(position.x + 1.0f, position.y + 1.0f); + rlEnd(); +} + +// Draw a line +void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2i(startPosX, startPosY); + rlVertex2i(endPosX, endPosY); + rlEnd(); +} + +// Draw a line (Vector version) +void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(startPos.x, startPos.y); + rlVertex2f(endPos.x, endPos.y); + rlEnd(); +} + +// Draw a line defining thickness +void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) +{ + Vector2 delta = {endPos.x-startPos.x, endPos.y-startPos.y}; + float length = sqrtf(delta.x*delta.x + delta.y*delta.y); + + if (length > 0 && thick > 0) + { + float scale = thick/(2*length); + Vector2 radius = {-scale*delta.y, scale*delta.x}; + Vector2 strip[] = {{startPos.x-radius.x, startPos.y-radius.y}, {startPos.x+radius.x, startPos.y+radius.y}, + {endPos.x-radius.x, endPos.y-radius.y}, {endPos.x+radius.x, endPos.y+radius.y}}; + + DrawTriangleStrip(strip, 4, color); + } +} + +// Draw line using cubic-bezier curves in-out +void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) +{ +#ifndef BEZIER_LINE_DIVISIONS + #define BEZIER_LINE_DIVISIONS 24 // Bezier line divisions +#endif + + Vector2 previous = startPos; + Vector2 current; + + for (int i = 1; i <= BEZIER_LINE_DIVISIONS; i++) + { + // Cubic easing in-out + // NOTE: Easing is calculated only for y position value + current.y = EaseCubicInOut((float)i, startPos.y, endPos.y - startPos.y, (float)BEZIER_LINE_DIVISIONS); + current.x = previous.x + (endPos.x - startPos.x)/ (float)BEZIER_LINE_DIVISIONS; + + DrawLineEx(previous, current, thick, color); + + previous = current; + } +} + +// Draw line using quadratic bezier curves with a control point +void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color) +{ + const float step = 1.0f/BEZIER_LINE_DIVISIONS; + + Vector2 previous = startPos; + Vector2 current = { 0 }; + float t = 0.0f; + + for (int i = 0; i <= BEZIER_LINE_DIVISIONS; i++) + { + t = step*i; + float a = powf(1 - t, 2); + float b = 2*(1 - t)*t; + float c = powf(t, 2); + + // NOTE: The easing functions aren't suitable here because they don't take a control point + current.y = a*startPos.y + b*controlPos.y + c*endPos.y; + current.x = a*startPos.x + b*controlPos.x + c*endPos.x; + + DrawLineEx(previous, current, thick, color); + + previous = current; + } +} + +// Draw lines sequence +void DrawLineStrip(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 2) + { + rlCheckRenderBatchLimit(pointsCount); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < pointsCount - 1; i++) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); + } +} + +// Draw a color-filled circle +void DrawCircle(int centerX, int centerY, float radius, Color color) +{ + DrawCircleV((Vector2){ (float)centerX, (float)centerY }, radius, color); +} + +// Draw a piece of a circle +void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*segments/2); + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + // NOTE: Every QUAD actually represents two segments + for (int i = 0; i < segments/2; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength*2))*radius, center.y + cosf(DEG2RAD*(angle + stepLength*2))*radius); + + angle += (stepLength*2); + } + + // NOTE: In case number of segments is odd, we add one last piece to the cake + if (segments%2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + rlEnd(); + + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(3*segments); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + // Hide the cap lines when the circle is full + bool showCapLines = true; + int limit = 2*(segments + 2); + if ((int)(endAngle - startAngle)%360 == 0) { limit = 2*segments; showCapLines = false; } + + rlCheckRenderBatchLimit(limit); + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + } + rlEnd(); +} + +// Draw a gradient-filled circle +// NOTE: Gradient goes from center (color1) to border (color2) +void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) +{ + rlCheckRenderBatchLimit(3*36); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color1.r, color1.g, color1.b, color1.a); + rlVertex2f((float)centerX, (float)centerY); + rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlVertex2f((float)centerX + sinf(DEG2RAD*i)*radius, (float)centerY + cosf(DEG2RAD*i)*radius); + rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlVertex2f((float)centerX + sinf(DEG2RAD*(i + 10))*radius, (float)centerY + cosf(DEG2RAD*(i + 10))*radius); + } + rlEnd(); +} + +// Draw a color-filled circle (Vector version) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +void DrawCircleV(Vector2 center, float radius, Color color) +{ + DrawCircleSector(center, radius, 0, 360, 36, color); +} + +// Draw circle outline +void DrawCircleLines(int centerX, int centerY, float radius, Color color) +{ + rlCheckRenderBatchLimit(2*36); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) + for (int i = 0; i < 360; i += 10) + { + rlVertex2f(centerX + sinf(DEG2RAD*i)*radius, centerY + cosf(DEG2RAD*i)*radius); + rlVertex2f(centerX + sinf(DEG2RAD*(i + 10))*radius, centerY + cosf(DEG2RAD*(i + 10))*radius); + } + rlEnd(); +} + +// Draw ellipse +void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + rlCheckRenderBatchLimit(3*36); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f((float)centerX, (float)centerY); + rlVertex2f((float)centerX + sinf(DEG2RAD*i)*radiusH, (float)centerY + cosf(DEG2RAD*i)*radiusV); + rlVertex2f((float)centerX + sinf(DEG2RAD*(i + 10))*radiusH, (float)centerY + cosf(DEG2RAD*(i + 10))*radiusV); + } + rlEnd(); +} + +// Draw ellipse outline +void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + rlCheckRenderBatchLimit(2*36); + + rlBegin(RL_LINES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(centerX + sinf(DEG2RAD*i)*radiusH, centerY + cosf(DEG2RAD*i)*radiusV); + rlVertex2f(centerX + sinf(DEG2RAD*(i + 10))*radiusH, centerY + cosf(DEG2RAD*(i + 10))*radiusV); + } + rlEnd(); +} + +void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + // Not a ring + if (innerRadius <= 0.0f) + { + DrawCircleSector(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*segments); + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + rlEnd(); + + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(6*segments); + + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); + + if (segments <= 0) segments = minSegments; + } + + if (innerRadius <= 0.0f) + { + DrawCircleSectorLines(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + bool showCapLines = true; + int limit = 4*(segments + 1); + if ((int)(endAngle - startAngle)%360 == 0) { limit = 4*segments; showCapLines = false; } + + rlCheckRenderBatchLimit(limit); + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + } + rlEnd(); +} + +// Draw a color-filled rectangle +void DrawRectangle(int posX, int posY, int width, int height, Color color) +{ + DrawRectangleV((Vector2){ (float)posX, (float)posY }, (Vector2){ (float)width, (float)height }, color); +} + +// Draw a color-filled rectangle (Vector version) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +void DrawRectangleV(Vector2 position, Vector2 size, Color color) +{ + DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle +void DrawRectangleRec(Rectangle rec, Color color) +{ + DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle with pro parameters +void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) +{ + rlCheckRenderBatchLimit(4); + + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = rec.x - origin.x; + float y = rec.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + rec.width, y }; + bottomLeft = (Vector2){ x, y + rec.height }; + bottomRight = (Vector2){ x + rec.width, y + rec.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = rec.x; + float y = rec.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + rec.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + rec.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + rec.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + rec.height)*cosRotation; + + bottomRight.x = x + (dx + rec.width)*cosRotation - (dy + rec.height)*sinRotation; + bottomRight.y = y + (dx + rec.width)*sinRotation + (dy + rec.height)*cosRotation; + } + + rlSetTexture(texShapes.id); + rlBegin(RL_QUADS); + + rlNormal3f(0.0f, 0.0f, 1.0f); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(topLeft.x, topLeft.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(bottomRight.x, bottomRight.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + rlSetTexture(0); +} + +// Draw a vertical-gradient-filled rectangle +// NOTE: Gradient goes from bottom (color1) to top (color2) +void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color2, color2, color1); +} + +// Draw a horizontal-gradient-filled rectangle +// NOTE: Gradient goes from bottom (color1) to top (color2) +void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2) +{ + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color1, color2, color2); +} + +// Draw a gradient-filled rectangle +// NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise +void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) +{ + rlSetTexture(texShapes.id); + + rlPushMatrix(); + rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); + + // NOTE: Default raylib font character 95 is a white square + rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(rec.x, rec.y); + + rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(rec.x, rec.y + rec.height); + + rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); + + rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y); + rlEnd(); + rlPopMatrix(); + + rlSetTexture(0); +} + +// Draw rectangle outline +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +void DrawRectangleLines(int posX, int posY, int width, int height, Color color) +{ +#if defined(SUPPORT_QUADS_DRAW_MODE) + DrawRectangle(posX, posY, width, 1, color); + DrawRectangle(posX + width - 1, posY + 1, 1, height - 2, color); + DrawRectangle(posX, posY + height - 1, width, 1, color); + DrawRectangle(posX, posY + 1, 1, height - 2, color); +#else + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2i(posX + 1, posY + 1); + rlVertex2i(posX + width, posY + 1); + + rlVertex2i(posX + width, posY + 1); + rlVertex2i(posX + width, posY + height); + + rlVertex2i(posX + width, posY + height); + rlVertex2i(posX + 1, posY + height); + + rlVertex2i(posX + 1, posY + height); + rlVertex2i(posX + 1, posY + 1); + rlEnd(); +#endif +} + +// Draw rectangle outline with extended parameters +void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) +{ + if ((lineThick > rec.width) || (lineThick > rec.height)) + { + if (rec.width > rec.height) lineThick = (int)rec.height/2; + else if (rec.width < rec.height) lineThick = (int)rec.width/2; + } + + // When rec = { x, y, 8.0f, 6.0f } and lineThick = 2, the following + // four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight): + // + // TTTTTTTT + // TTTTTTTT + // LL RR + // LL RR + // BBBBBBBB + // BBBBBBBB + // + float thick = (float)lineThick; + Rectangle top = { rec.x, rec.y, rec.width, thick }; + Rectangle bottom = { rec.x, rec.y - thick + rec.height, rec.width, thick }; + Rectangle left = { rec.x, rec.y + thick, thick, rec.height - thick*2.0f }; + Rectangle right = { rec.x - thick + rec.width, rec.y + thick, thick, rec.height - thick*2.0f }; + + DrawRectangleRec(top, color); + DrawRectangleRec(bottom, color); + DrawRectangleRec(left, color); + DrawRectangleRec(right, color); +} + +// Draw rectangle with rounded edges +void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color) +{ + // Not a rounded rectangle + if ((roundness <= 0.0f) || (rec.width < 1) || (rec.height < 1 )) + { + DrawRectangleRec(rec, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)(ceilf(2*PI/th)/4.0f); + if (segments <= 0) segments = 4; + } + + float stepLength = 90.0f/(float)segments; + + /* + Quick sketch to make sense of all of this, + there are 9 parts to draw, also mark the 12 points we'll use + + P0____________________P1 + /| |\ + /1| 2 |3\ + P7 /__|____________________|__\ P2 + | |P8 P9| | + | 8 | 9 | 4 | + | __|____________________|__ | + P6 \ |P11 P10| / P3 + \7| 6 |5/ + \|____________________|/ + P5 P4 + */ + // Coordinates of the 12 points that define the rounded rect + const Vector2 point[12] = { + {(float)rec.x + radius, rec.y}, {(float)(rec.x + rec.width) - radius, rec.y}, { rec.x + rec.width, (float)rec.y + radius }, // PO, P1, P2 + {rec.x + rec.width, (float)(rec.y + rec.height) - radius}, {(float)(rec.x + rec.width) - radius, rec.y + rec.height}, // P3, P4 + {(float)rec.x + radius, rec.y + rec.height}, { rec.x, (float)(rec.y + rec.height) - radius}, {rec.x, (float)rec.y + radius}, // P5, P6, P7 + {(float)rec.x + radius, (float)rec.y + radius}, {(float)(rec.x + rec.width) - radius, (float)rec.y + radius}, // P8, P9 + {(float)(rec.x + rec.width) - radius, (float)(rec.y + rec.height) - radius}, {(float)rec.x + radius, (float)(rec.y + rec.height) - radius} // P10, P11 + }; + + const Vector2 centers[4] = { point[8], point[9], point[10], point[11] }; + const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(16*segments/2 + 5*4); + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + // NOTE: Every QUAD actually represents two segments + for (int i = 0; i < segments/2; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength*2))*radius, center.y + cosf(DEG2RAD*(angle + stepLength*2))*radius); + angle += (stepLength*2); + } + + // NOTE: In case number of segments is odd, we add one last piece to the cake + if (segments%2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + + rlEnd(); + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(12*segments + 5*6); // 4 corners with 3 vertices per segment + 5 rectangles with 6 vertices each + + rlBegin(RL_TRIANGLES); + + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); + angle += stepLength; + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[4].x, point[4].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[11].x, point[11].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[10].x, point[10].y); + rlEnd(); +#endif +} + +// Draw rectangle with rounded edges outline +void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color) +{ + if (lineThick < 0) lineThick = 0; + + // Not a rounded rectangle + if (roundness <= 0.0f) + { + DrawRectangleLinesEx((Rectangle){rec.x-lineThick, rec.y-lineThick, rec.width+2*lineThick, rec.height+2*lineThick}, lineThick, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)(ceilf(2*PI/th)/2.0f); + if (segments <= 0) segments = 4; + } + + float stepLength = 90.0f/(float)segments; + const float outerRadius = radius + (float)lineThick, innerRadius = radius; + + /* + Quick sketch to make sense of all of this, + marks the 16 + 4(corner centers P16-19) points we'll use + + P0 ================== P1 + // P8 P9 \\ + // \\ + P7 // P15 P10 \\ P2 + || *P16 P17* || + || || + || P14 P11 || + P6 \\ *P19 P18* // P3 + \\ // + \\ P13 P12 // + P5 ================== P4 + */ + const Vector2 point[16] = { + {(float)rec.x + innerRadius, rec.y - lineThick}, {(float)(rec.x + rec.width) - innerRadius, rec.y - lineThick}, { rec.x + rec.width + lineThick, (float)rec.y + innerRadius }, // PO, P1, P2 + {rec.x + rec.width + lineThick, (float)(rec.y + rec.height) - innerRadius}, {(float)(rec.x + rec.width) - innerRadius, rec.y + rec.height + lineThick}, // P3, P4 + {(float)rec.x + innerRadius, rec.y + rec.height + lineThick}, { rec.x - lineThick, (float)(rec.y + rec.height) - innerRadius}, {rec.x - lineThick, (float)rec.y + innerRadius}, // P5, P6, P7 + {(float)rec.x + innerRadius, rec.y}, {(float)(rec.x + rec.width) - innerRadius, rec.y}, // P8, P9 + { rec.x + rec.width, (float)rec.y + innerRadius }, {rec.x + rec.width, (float)(rec.y + rec.height) - innerRadius}, // P10, P11 + {(float)(rec.x + rec.width) - innerRadius, rec.y + rec.height}, {(float)rec.x + innerRadius, rec.y + rec.height}, // P12, P13 + { rec.x, (float)(rec.y + rec.height) - innerRadius}, {rec.x, (float)rec.y + innerRadius} // P14, P15 + }; + + const Vector2 centers[4] = { + {(float)rec.x + innerRadius, (float)rec.y + innerRadius}, {(float)(rec.x + rec.width) - innerRadius, (float)rec.y + innerRadius}, // P16, P17 + {(float)(rec.x + rec.width) - innerRadius, (float)(rec.y + rec.height) - innerRadius}, {(float)rec.x + innerRadius, (float)(rec.y + rec.height) - innerRadius} // P18, P19 + }; + + const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; + + if (lineThick > 1) + { +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*4*segments + 4*4); // 4 corners with 4 vertices for each segment + 4 rectangles with 4 vertices each + + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[13].x, point[13].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[12].x, point[12].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[15].x, point[15].y); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(point[14].x, point[14].y); + + rlEnd(); + rlSetTexture(0); +#else + rlCheckRenderBatchLimit(4*6*segments + 4*6); // 4 corners with 6(2*3) vertices for each segment + 4 rectangles with 6 vertices each + + rlBegin(RL_TRIANGLES); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[12].x, point[12].y); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[4].x, point[4].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[14].x, point[14].y); + rlVertex2f(point[15].x, point[15].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[14].x, point[14].y); + rlEnd(); +#endif + } + else + { + // Use LINES to draw the outline + rlCheckRenderBatchLimit(8*segments + 4*2); // 4 corners with 2 vertices for each segment + 4 rectangles with 2 vertices each + + rlBegin(RL_LINES); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); + angle += stepLength; + } + } + + // And now the remaining 4 lines + for (int i = 0; i < 8; i += 2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[i].x, point[i].y); + rlVertex2f(point[i + 1].x, point[i + 1].y); + } + + rlEnd(); + } +} + +// Draw a triangle +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + rlCheckRenderBatchLimit(4); + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(v1.x, v1.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(v3.x, v3.y); + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + rlEnd(); +#endif +} + +// Draw a triangle using lines +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + rlCheckRenderBatchLimit(6); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + + rlVertex2f(v3.x, v3.y); + rlVertex2f(v1.x, v1.y); + rlEnd(); +} + +// Draw a triangle fan defined by points +// NOTE: First vertex provided is the center, shared by all triangles +// By default, following vertex should be provided in counter-clockwise order +void DrawTriangleFan(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + rlCheckRenderBatchLimit((pointsCount - 2)*4); + + rlSetTexture(texShapes.id); + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 1; i < pointsCount - 1; i++) + { + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(points[0].x, points[0].y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(points[i].x, points[i].y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); + rlSetTexture(0); + } +} + +// Draw a triangle strip defined by points +// NOTE: Every new vertex connects with previous two +void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color) +{ + if (pointsCount >= 3) + { + rlCheckRenderBatchLimit(3*(pointsCount - 2)); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 2; i < pointsCount; i++) + { + if ((i%2) == 0) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + } + else + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + } + } + rlEnd(); + } +} + +// Draw a regular polygon of n sides (Vector version) +void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = 0.0f; + + rlCheckRenderBatchLimit(4*(360/sides)); + + rlPushMatrix(); + rlTranslatef(center.x, center.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(0, 0); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + centralAngle += 360.0f/(float)sides; + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + } + rlEnd(); + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(0, 0); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + centralAngle += 360.0f/(float)sides; + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + } + rlEnd(); +#endif + rlPopMatrix(); +} + +// Draw a polygon outline of n sides +void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = 0.0f; + + rlCheckRenderBatchLimit(3*(360/sides)); + + rlPushMatrix(); + rlTranslatef(center.x, center.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + + rlBegin(RL_LINES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + centralAngle += 360.0f/(float)sides; + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + } + rlEnd(); + rlPopMatrix(); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Collision Detection functions +//---------------------------------------------------------------------------------- + +// Check if point is inside rectangle +bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Check if point is inside circle +bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius) +{ + return CheckCollisionCircles(point, 0, center, radius); +} + +// Check if point is inside a triangle defined by three points (p1, p2, p3) +bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3) +{ + bool collision = false; + + float alpha = ((p2.y - p3.y)*(point.x - p3.x) + (p3.x - p2.x)*(point.y - p3.y)) / + ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y)); + + float beta = ((p3.y - p1.y)*(point.x - p3.x) + (p1.x - p3.x)*(point.y - p3.y)) / + ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0) && (beta > 0) && (gamma > 0)) collision = true; + + return collision; +} + +// Check collision between two rectangles +bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) +{ + bool collision = false; + + if ((rec1.x < (rec2.x + rec2.width) && (rec1.x + rec1.width) > rec2.x) && + (rec1.y < (rec2.y + rec2.height) && (rec1.y + rec1.height) > rec2.y)) collision = true; + + return collision; +} + +// Check collision between two circles +bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2) +{ + bool collision = false; + + float dx = center2.x - center1.x; // X distance between centers + float dy = center2.y - center1.y; // Y distance between centers + + float distance = sqrtf(dx*dx + dy*dy); // Distance between centers + + if (distance <= (radius1 + radius2)) collision = true; + + return collision; +} + +// Check collision between circle and rectangle +// NOTE: Reviewed version to take into account corner limit case +bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) +{ + int recCenterX = (int)(rec.x + rec.width/2.0f); + int recCenterY = (int)(rec.y + rec.height/2.0f); + + float dx = fabsf(center.x - (float)recCenterX); + float dy = fabsf(center.y - (float)recCenterY); + + if (dx > (rec.width/2.0f + radius)) { return false; } + if (dy > (rec.height/2.0f + radius)) { return false; } + + if (dx <= (rec.width/2.0f)) { return true; } + if (dy <= (rec.height/2.0f)) { return true; } + + float cornerDistanceSq = (dx - rec.width/2.0f)*(dx - rec.width/2.0f) + + (dy - rec.height/2.0f)*(dy - rec.height/2.0f); + + return (cornerDistanceSq <= (radius*radius)); +} + +// Check the collision between two lines defined by two points each, returns collision point by reference +bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint) +{ + const float div = (endPos2.y - startPos2.y)*(endPos1.x - startPos1.x) - (endPos2.x - startPos2.x)*(endPos1.y - startPos1.y); + + if (div == 0.0f) return false; // WARNING: This check could not work due to float precision rounding issues... + + const float xi = ((startPos2.x - endPos2.x)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.x - endPos1.x)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; + const float yi = ((startPos2.y - endPos2.y)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.y - endPos1.y)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; + + if (xi < fminf(startPos1.x, endPos1.x) || xi > fmaxf(startPos1.x, endPos1.x)) return false; + if (xi < fminf(startPos2.x, endPos2.x) || xi > fmaxf(startPos2.x, endPos2.x)) return false; + if (yi < fminf(startPos1.y, endPos1.y) || yi > fmaxf(startPos1.y, endPos1.y)) return false; + if (yi < fminf(startPos2.y, endPos2.y) || yi > fmaxf(startPos2.y, endPos2.y)) return false; + + if (collisionPoint != 0) + { + collisionPoint->x = xi; + collisionPoint->y = yi; + } + + return true; +} + +// Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold) +{ + bool collision = false; + float dxc = point.x - p1.x; + float dyc = point.y - p1.y; + float dxl = p2.x - p1.x; + float dyl = p2.y - p1.y; + float cross = dxc*dyl - dyc*dxl; + + if (fabsf(cross) < (threshold*fmaxf(fabsf(dxl), fabsf(dyl)))) + { + if (fabsf(dxl) >= fabsf(dyl)) collision = (dxl > 0)? ((p1.x <= point.x) && (point.x <= p2.x)) : ((p2.x <= point.x) && (point.x <= p1.x)); + else collision = (dyl > 0)? ((p1.y <= point.y) && (point.y <= p2.y)) : ((p2.y <= point.y) && (point.y <= p1.y)); + } + + return collision; +} + +// Get collision rectangle for two rectangles collision +Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) +{ + Rectangle rec = { 0, 0, 0, 0 }; + + if (CheckCollisionRecs(rec1, rec2)) + { + float dxx = fabsf(rec1.x - rec2.x); + float dyy = fabsf(rec1.y - rec2.y); + + if (rec1.x <= rec2.x) + { + if (rec1.y <= rec2.y) + { + rec.x = rec2.x; + rec.y = rec2.y; + rec.width = rec1.width - dxx; + rec.height = rec1.height - dyy; + } + else + { + rec.x = rec2.x; + rec.y = rec1.y; + rec.width = rec1.width - dxx; + rec.height = rec2.height - dyy; + } + } + else + { + if (rec1.y <= rec2.y) + { + rec.x = rec1.x; + rec.y = rec2.y; + rec.width = rec2.width - dxx; + rec.height = rec1.height - dyy; + } + else + { + rec.x = rec1.x; + rec.y = rec1.y; + rec.width = rec2.width - dxx; + rec.height = rec2.height - dyy; + } + } + + if (rec1.width > rec2.width) + { + if (rec.width >= rec2.width) rec.width = rec2.width; + } + else + { + if (rec.width >= rec1.width) rec.width = rec1.width; + } + + if (rec1.height > rec2.height) + { + if (rec.height >= rec2.height) rec.height = rec2.height; + } + else + { + if (rec.height >= rec1.height) rec.height = rec1.height; + } + } + + return rec; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Cubic easing in-out +// NOTE: Required for DrawLineBezier() +static float EaseCubicInOut(float t, float b, float c, float d) +{ + if ((t /= 0.5f*d) < 1) return 0.5f*c*t*t*t + b; + + t -= 2; + + return 0.5f*c*(t*t*t + 2.0f) + b; +} diff --git a/raylib_pi4_test/shell.html b/raylib_pi4_test/shell.html new file mode 100644 index 0000000..507b35a --- /dev/null +++ b/raylib_pi4_test/shell.html @@ -0,0 +1,327 @@ + + + + + + + raylib HTML5 GAME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + {{{ SCRIPT }}} + + diff --git a/raylib_pi4_test/text.c b/raylib_pi4_test/text.c new file mode 100644 index 0000000..3a49963 --- /dev/null +++ b/raylib_pi4_test/text.c @@ -0,0 +1,1878 @@ +/********************************************************************************************** +* +* raylib.text - Basic functions to load Fonts and draw Text +* +* CONFIGURATION: +* +* #define SUPPORT_FILEFORMAT_FNT +* #define SUPPORT_FILEFORMAT_TTF +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define SUPPORT_DEFAULT_FONT +* Load default raylib font on initialization to be used by DrawText() and MeasureText(). +* If no default font loaded, DrawTextEx() and MeasureTextEx() are required. +* +* #define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH +* TextSplit() function static buffer max size +* +* #define MAX_TEXTSPLIT_COUNT +* TextSplit() function static substrings pointers array (pointing to static buffer) +* +* +* DEPENDENCIES: +* stb_truetype - Load TTF file and rasterize characters data +* stb_rect_pack - Rectangles packing algorythms, required for font atlas generation +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include // Required for: malloc(), free() +#include // Required for: vsprintf() +#include // Required for: strcmp(), strstr(), strcpy(), strncpy() [Used in TextReplace()], sscanf() [Used in LoadBMFont()] +#include // Required for: va_list, va_start(), vsprintf(), va_end() [Used in TextFormat()] +#include // Requried for: toupper(), tolower() [Used in TextToUpper(), TextToLower()] + +#include "utils.h" // Required for: LoadFileText() + +#if defined(SUPPORT_FILEFORMAT_TTF) + #define STB_RECT_PACK_IMPLEMENTATION + #include "external/stb_rect_pack.h" // Required for: ttf font rectangles packaging + + #define STBTT_STATIC + #define STB_TRUETYPE_IMPLEMENTATION + #include "external/stb_truetype.h" // Required for: ttf font data reading +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_TEXT_BUFFER_LENGTH + #define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions: + // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal(), TextSplit() +#endif +#ifndef MAX_TEXT_UNICODE_CHARS + #define MAX_TEXT_UNICODE_CHARS 512 // Maximum number of unicode codepoints: GetCodepoints() +#endif +#ifndef MAX_TEXTSPLIT_COUNT + #define MAX_TEXTSPLIT_COUNT 128 // Maximum number of substrings to split: TextSplit() +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global variables +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_DEFAULT_FONT) +// Default font provided by raylib +// NOTE: Default font is loaded on InitWindow() and disposed on CloseWindow() [module: core] +static Font defaultFont = { 0 }; +#endif + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by text) +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_FNT) +static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file) +#endif + +#if defined(SUPPORT_DEFAULT_FONT) +extern void LoadFontDefault(void); +extern void UnloadFontDefault(void); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_DEFAULT_FONT) + +// Load raylib default font +extern void LoadFontDefault(void) +{ + #define BIT_CHECK(a,b) ((a) & (1u << (b))) + + // NOTE: Using UTF8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement + // Ref: http://www.utf8-chartable.de/unicode-utf8-table.pl + + defaultFont.charsCount = 224; // Number of chars included in our default font + defaultFont.charsPadding = 0; // Characters padding + + // Default font is directly defined here (data generated from a sprite font image) + // This way, we reconstruct Font without creating large global variables + // This data is automatically allocated to Stack and automatically deallocated at the end of this function + unsigned int defaultFontData[512] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f, + 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de, + 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f, + 0x00200000, 0x8001b000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xc0000fbe, 0xfbf7e00f, 0x5fbf7e7d, 0x0050bee8, 0x440808a2, 0x0a142fe8, 0x50810285, 0x0050a048, + 0x49e428a2, 0x0a142828, 0x40810284, 0x0048a048, 0x10020fbe, 0x09f7ebaf, 0xd89f3e84, 0x0047a04f, 0x09e48822, 0x0a142aa1, 0x50810284, 0x0048a048, + 0x04082822, 0x0a142fa0, 0x50810285, 0x0050a248, 0x00008fbe, 0xfbf42021, 0x5f817e7d, 0x07d09ce8, 0x00008000, 0x00000fe0, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000c0180, + 0xdfbf4282, 0x0bfbf7ef, 0x42850505, 0x004804bf, 0x50a142c6, 0x08401428, 0x42852505, 0x00a808a0, 0x50a146aa, 0x08401428, 0x42852505, 0x00081090, + 0x5fa14a92, 0x0843f7e8, 0x7e792505, 0x00082088, 0x40a15282, 0x08420128, 0x40852489, 0x00084084, 0x40a16282, 0x0842022a, 0x40852451, 0x00088082, + 0xc0bf4282, 0xf843f42f, 0x7e85fc21, 0x3e0900bf, 0x00000000, 0x00000004, 0x00000000, 0x000c0180, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000402, 0x41482000, 0x00000000, 0x00000800, + 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820, + 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0, + 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000f01, 0x00000000, 0x06000000, 0x24000000, 0x00000f01, 0x00000000, 0x09108000, + 0x24fa28a2, 0x00000f01, 0x00000000, 0x013e0000, 0x2242252a, 0x00000f52, 0x00000000, 0x038a8000, 0x2422222a, 0x00000f29, 0x00000000, 0x010a8000, + 0x2412252a, 0x00000f01, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000f01, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000, + 0x0003e000, 0x00000f00, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03, + 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202, + 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002, + 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002, + 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010, + 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7, + 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a, + 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b, + 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210, + 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe, + 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2, + 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000, + 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, + 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000, + 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + + int charsHeight = 10; + int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically + + int charsWidth[224] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, + 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 5, 7, 1, 5, 3, 7, 3, 5, 4, 1, 7, 4, 3, 5, 3, 3, 2, 5, 6, 1, 2, 2, 3, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 3, 3, 3, 3, 7, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 4, 6, + 5, 5, 5, 5, 5, 5, 9, 5, 5, 5, 5, 5, 2, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5 }; + + // Re-construct image from defaultFontData and generate OpenGL texture + //---------------------------------------------------------------------- + Image imFont = { + .data = calloc(128*128, 2), // 2 bytes per pixel (gray + alpha) + .width = 128, + .height = 128, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, + .mipmaps = 1 + }; + + // Fill image.data with defaultFontData (convert from bit to pixel!) + for (int i = 0, counter = 0; i < imFont.width*imFont.height; i += 32) + { + for (int j = 31; j >= 0; j--) + { + if (BIT_CHECK(defaultFontData[counter], j)) + { + // NOTE: We are unreferencing data as short, so, + // we must consider data as little-endian order (alpha + gray) + ((unsigned short *)imFont.data)[i + j] = 0xffff; + } + else ((unsigned short *)imFont.data)[i + j] = 0x00ff; + } + + counter++; + } + + defaultFont.texture = LoadTextureFromImage(imFont); + + // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount + //------------------------------------------------------------------------------ + + // Allocate space for our characters info data + // NOTE: This memory should be freed at end! --> CloseWindow() + defaultFont.chars = (CharInfo *)RL_MALLOC(defaultFont.charsCount*sizeof(CharInfo)); + defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.charsCount*sizeof(Rectangle)); + + int currentLine = 0; + int currentPosX = charsDivisor; + int testPosX = charsDivisor; + + for (int i = 0; i < defaultFont.charsCount; i++) + { + defaultFont.chars[i].value = 32 + i; // First char is 32 + + defaultFont.recs[i].x = (float)currentPosX; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + defaultFont.recs[i].width = (float)charsWidth[i]; + defaultFont.recs[i].height = (float)charsHeight; + + testPosX += (int)(defaultFont.recs[i].width + (float)charsDivisor); + + if (testPosX >= defaultFont.texture.width) + { + currentLine++; + currentPosX = 2*charsDivisor + charsWidth[i]; + testPosX = currentPosX; + + defaultFont.recs[i].x = (float)charsDivisor; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + } + else currentPosX = testPosX; + + // NOTE: On default font character offsets and xAdvance are not required + defaultFont.chars[i].offsetX = 0; + defaultFont.chars[i].offsetY = 0; + defaultFont.chars[i].advanceX = 0; + + // Fill character image data from fontClear data + defaultFont.chars[i].image = ImageFromImage(imFont, defaultFont.recs[i]); + } + + UnloadImage(imFont); + + defaultFont.baseSize = (int)defaultFont.recs[0].height; + + TRACELOG(LOG_INFO, "FONT: Default font loaded successfully"); +} + +// Unload raylib default font +extern void UnloadFontDefault(void) +{ + for (int i = 0; i < defaultFont.charsCount; i++) UnloadImage(defaultFont.chars[i].image); + UnloadTexture(defaultFont.texture); + RL_FREE(defaultFont.chars); + RL_FREE(defaultFont.recs); +} +#endif // SUPPORT_DEFAULT_FONT + +// Get the default font, useful to be used with extended parameters +Font GetFontDefault() +{ +#if defined(SUPPORT_DEFAULT_FONT) + return defaultFont; +#else + Font font = { 0 }; + return font; +#endif +} + +// Load Font from file into GPU memory (VRAM) +Font LoadFont(const char *fileName) +{ + // Default values for ttf font generation +#ifndef FONT_TTF_DEFAULT_SIZE + #define FONT_TTF_DEFAULT_SIZE 32 // TTF font generation default char size (char-height) +#endif +#ifndef FONT_TTF_DEFAULT_NUMCHARS + #define FONT_TTF_DEFAULT_NUMCHARS 95 // TTF font generation default charset: 95 glyphs (ASCII 32..126) +#endif +#ifndef FONT_TTF_DEFAULT_FIRST_CHAR + #define FONT_TTF_DEFAULT_FIRST_CHAR 32 // TTF font generation default first char for image sprite font (32-Space) +#endif +#ifndef FONT_TTF_DEFAULT_CHARS_PADDING + #define FONT_TTF_DEFAULT_CHARS_PADDING 4 // TTF font generation default chars padding +#endif + + Font font = { 0 }; + +#if defined(SUPPORT_FILEFORMAT_TTF) + if (IsFileExtension(fileName, ".ttf;.otf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS); + else +#endif +#if defined(SUPPORT_FILEFORMAT_FNT) + if (IsFileExtension(fileName, ".fnt")) font = LoadBMFont(fileName); + else +#endif + { + Image image = LoadImage(fileName); + if (image.data != NULL) font = LoadFontFromImage(image, MAGENTA, FONT_TTF_DEFAULT_FIRST_CHAR); + UnloadImage(image); + } + + if (font.texture.id == 0) + { + TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); + font = GetFontDefault(); + } + else SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default we set point filter (best performance) + + return font; +} + +// Load Font from TTF font file with generation parameters +// NOTE: You can pass an array with desired characters, those characters should be available in the font +// if array is NULL, default char set is selected 32..126 +Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount) +{ + Font font = { 0 }; + + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading font from memory data + font = LoadFontFromMemory(GetFileExtension(fileName), fileData, fileSize, fontSize, fontChars, charsCount); + + RL_FREE(fileData); + } + else font = GetFontDefault(); + + return font; +} + +// Load an Image font file (XNA style) +Font LoadFontFromImage(Image image, Color key, int firstChar) +{ +#ifndef MAX_GLYPHS_FROM_IMAGE + #define MAX_GLYPHS_FROM_IMAGE 256 // Maximum number of glyphs supported on image scan +#endif + + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + int charSpacing = 0; + int lineSpacing = 0; + + int x = 0; + int y = 0; + + // We allocate a temporal arrays for chars data measures, + // once we get the actual number of chars, we copy data to a sized arrays + int tempCharValues[MAX_GLYPHS_FROM_IMAGE]; + Rectangle tempCharRecs[MAX_GLYPHS_FROM_IMAGE]; + + Color *pixels = LoadImageColors(image); + + // Parse image data to get charSpacing and lineSpacing + for (y = 0; y < image.height; y++) + { + for (x = 0; x < image.width; x++) + { + if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; + } + + if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; + } + + charSpacing = x; + lineSpacing = y; + + int charHeight = 0; + int j = 0; + + while (!COLOR_EQUAL(pixels[(lineSpacing + j)*image.width + charSpacing], key)) j++; + + charHeight = j; + + // Check array values to get characters: value, x, y, w, h + int index = 0; + int lineToRead = 0; + int xPosToRead = charSpacing; + + // Parse image data to get rectangle sizes + while ((lineSpacing + lineToRead*(charHeight + lineSpacing)) < image.height) + { + while ((xPosToRead < image.width) && + !COLOR_EQUAL((pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead]), key)) + { + tempCharValues[index] = firstChar + index; + + tempCharRecs[index].x = (float)xPosToRead; + tempCharRecs[index].y = (float)(lineSpacing + lineToRead*(charHeight + lineSpacing)); + tempCharRecs[index].height = (float)charHeight; + + int charWidth = 0; + + while (!COLOR_EQUAL(pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead + charWidth], key)) charWidth++; + + tempCharRecs[index].width = (float)charWidth; + + index++; + + xPosToRead += (charWidth + charSpacing); + } + + lineToRead++; + xPosToRead = charSpacing; + } + + // NOTE: We need to remove key color borders from image to avoid weird + // artifacts on texture scaling when using TEXTURE_FILTER_BILINEAR or TEXTURE_FILTER_TRILINEAR + for (int i = 0; i < image.height*image.width; i++) if (COLOR_EQUAL(pixels[i], key)) pixels[i] = BLANK; + + // Create a new image with the processed color data (key color replaced by BLANK) + Image fontClear = { + .data = pixels, + .width = image.width, + .height = image.height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + // Create spritefont with all data parsed from image + Font font = { 0 }; + + font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture + font.charsCount = index; + font.charsPadding = 0; + + // We got tempCharValues and tempCharsRecs populated with chars data + // Now we move temp data to sized charValues and charRecs arrays + font.chars = (CharInfo *)RL_MALLOC(font.charsCount*sizeof(CharInfo)); + font.recs = (Rectangle *)RL_MALLOC(font.charsCount*sizeof(Rectangle)); + + for (int i = 0; i < font.charsCount; i++) + { + font.chars[i].value = tempCharValues[i]; + + // Get character rectangle in the font atlas texture + font.recs[i] = tempCharRecs[i]; + + // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0) + font.chars[i].offsetX = 0; + font.chars[i].offsetY = 0; + font.chars[i].advanceX = 0; + + // Fill character image data from fontClear data + font.chars[i].image = ImageFromImage(fontClear, tempCharRecs[i]); + } + + UnloadImage(fontClear); // Unload processed image once converted to texture + + font.baseSize = (int)font.recs[0].height; + + return font; +} + +// Load font from memory buffer, fileType refers to extension: i.e. ".ttf" +Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount) +{ + Font font = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + +#if defined(SUPPORT_FILEFORMAT_TTF) + if (TextIsEqual(fileExtLower, ".ttf") || + TextIsEqual(fileExtLower, ".otf")) + { + font.baseSize = fontSize; + font.charsCount = (charsCount > 0)? charsCount : 95; + font.charsPadding = 0; + font.chars = LoadFontData(fileData, dataSize, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT); + + if (font.chars != NULL) + { + font.charsPadding = FONT_TTF_DEFAULT_CHARS_PADDING; + + Image atlas = GenImageFontAtlas(font.chars, &font.recs, font.charsCount, font.baseSize, font.charsPadding, 0); + font.texture = LoadTextureFromImage(atlas); + + // Update chars[i].image to use alpha, required to be used on ImageDrawText() + for (int i = 0; i < font.charsCount; i++) + { + UnloadImage(font.chars[i].image); + font.chars[i].image = ImageFromImage(atlas, font.recs[i]); + } + + UnloadImage(atlas); + } + else font = GetFontDefault(); + } +#else + font = GetFontDefault(); +#endif + + return font; +} + +// Load font data for further use +// NOTE: Requires TTF font memory data and can generate SDF data +CharInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount, int type) +{ + // NOTE: Using some SDF generation default values, + // trades off precision with ability to handle *smaller* sizes +#ifndef FONT_SDF_CHAR_PADDING + #define FONT_SDF_CHAR_PADDING 4 // SDF font generation char padding +#endif +#ifndef FONT_SDF_ON_EDGE_VALUE + #define FONT_SDF_ON_EDGE_VALUE 128 // SDF font generation on edge value +#endif +#ifndef FONT_SDF_PIXEL_DIST_SCALE + #define FONT_SDF_PIXEL_DIST_SCALE 64.0f // SDF font generation pixel distance scale +#endif +#ifndef FONT_BITMAP_ALPHA_THRESHOLD + #define FONT_BITMAP_ALPHA_THRESHOLD 80 // Bitmap (B&W) font generation alpha threshold +#endif + + CharInfo *chars = NULL; + +#if defined(SUPPORT_FILEFORMAT_TTF) + // Load font data (including pixel data) from TTF memory file + // NOTE: Loaded information should be enough to generate font image atlas, using any packaging method + if (fileData != NULL) + { + int genFontChars = false; + stbtt_fontinfo fontInfo = { 0 }; + + if (stbtt_InitFont(&fontInfo, (unsigned char *)fileData, 0)) // Init font for data reading + { + // Calculate font scale factor + float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); + + // Calculate font basic metrics + // NOTE: ascent is equivalent to font baseline + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); + + // In case no chars count provided, default to 95 + charsCount = (charsCount > 0)? charsCount : 95; + + // Fill fontChars in case not provided externally + // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space) + + if (fontChars == NULL) + { + fontChars = (int *)RL_MALLOC(charsCount*sizeof(int)); + for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32; + genFontChars = true; + } + + chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + int chw = 0, chh = 0; // Character width and height (on generation) + int ch = fontChars[i]; // Character value to get info for + chars[i].value = ch; + + // Render a unicode codepoint to a bitmap + // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap + // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be + // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide + + if (type != FONT_SDF) chars[i].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else if (ch != 32) chars[i].image.data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, FONT_SDF_CHAR_PADDING, FONT_SDF_ON_EDGE_VALUE, FONT_SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + else chars[i].image.data = NULL; + + stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); + chars[i].advanceX = (int)((float)chars[i].advanceX*scaleFactor); + + // Load characters images + chars[i].image.width = chw; + chars[i].image.height = chh; + chars[i].image.mipmaps = 1; + chars[i].image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + + chars[i].offsetY += (int)((float)ascent*scaleFactor); + + // NOTE: We create an empty image for space character, it could be further required for atlas packing + if (ch == 32) + { + Image imSpace = { + .data = calloc(chars[i].advanceX*fontSize, 2), + .width = chars[i].advanceX, + .height = fontSize, + .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE, + .mipmaps = 1 + }; + + chars[i].image = imSpace; + } + + if (type == FONT_BITMAP) + { + // Aliased bitmap (black & white) font generation, avoiding anti-aliasing + // NOTE: For optimum results, bitmap font should be generated at base pixel size + for (int p = 0; p < chw*chh; p++) + { + if (((unsigned char *)chars[i].image.data)[p] < FONT_BITMAP_ALPHA_THRESHOLD) ((unsigned char *)chars[i].image.data)[p] = 0; + else ((unsigned char *)chars[i].image.data)[p] = 255; + } + } + + // Get bounding box for character (may be offset to account for chars that dip above or below the line) + /* + int chX1, chY1, chX2, chY2; + stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2); + + TRACELOGD("FONT: Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1); + TRACELOGD("FONT: Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1); + */ + } + } + else TRACELOG(LOG_WARNING, "FONT: Failed to process TTF font data"); + + if (genFontChars) RL_FREE(fontChars); + } +#endif + + return chars; +} + +// Generate image font atlas using chars info +// NOTE: Packing method: 0-Default, 1-Skyline +#if defined(SUPPORT_FILEFORMAT_TTF) +Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCount, int fontSize, int padding, int packMethod) +{ + Image atlas = { 0 }; + + if (chars == NULL) + { + TraceLog(LOG_WARNING, "FONT: Provided chars info not valid, returning empty image atlas"); + return atlas; + } + + *charRecs = NULL; + + // In case no chars count provided we suppose default of 95 + charsCount = (charsCount > 0)? charsCount : 95; + + // NOTE: Rectangles memory is loaded here! + Rectangle *recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); + + // Calculate image size based on required pixel area + // NOTE 1: Image is forced to be squared and POT... very conservative! + // NOTE 2: SDF font characters already contain an internal padding, + // so image size would result bigger than default font type + float requiredArea = 0; + for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].image.width + 2*padding)*(chars[i].image.height + 2*padding)); + float guessSize = sqrtf(requiredArea)*1.3f; + int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT + + atlas.width = imageSize; // Atlas bitmap width + atlas.height = imageSize; // Atlas bitmap height + atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + atlas.mipmaps = 1; + + // DEBUG: We can see padding in the generated image setting a gray background... + //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; + + if (packMethod == 0) // Use basic packing algorythm + { + int offsetX = padding; + int offsetY = padding; + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < charsCount; i++) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < chars[i].image.height; y++) + { + for (int x = 0; x < chars[i].image.width; x++) + { + ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x]; + } + } + + // Fill chars rectangles in atlas info + recs[i].x = (float)offsetX; + recs[i].y = (float)offsetY; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; + + // Move atlas position X for next character drawing + offsetX += (chars[i].image.width + 2*padding); + + if (offsetX >= (atlas.width - chars[i].image.width - 2*padding)) + { + offsetX = padding; + + // NOTE: Be careful on offsetY for SDF fonts, by default SDF + // use an internal padding of 4 pixels, it means char rectangle + // height is bigger than fontSize, it could be up to (fontSize + 8) + offsetY += (fontSize + 2*padding); + + if (offsetY > (atlas.height - fontSize - padding)) break; + } + } + } + else if (packMethod == 1) // Use Skyline rect packing algorythm (stb_pack_rect) + { + stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context)); + stbrp_node *nodes = (stbrp_node *)RL_MALLOC(charsCount*sizeof(*nodes)); + + stbrp_init_target(context, atlas.width, atlas.height, nodes, charsCount); + stbrp_rect *rects = (stbrp_rect *)RL_MALLOC(charsCount*sizeof(stbrp_rect)); + + // Fill rectangles for packaging + for (int i = 0; i < charsCount; i++) + { + rects[i].id = i; + rects[i].w = chars[i].image.width + 2*padding; + rects[i].h = chars[i].image.height + 2*padding; + } + + // Package rectangles into atlas + stbrp_pack_rects(context, rects, charsCount); + + for (int i = 0; i < charsCount; i++) + { + // It return char rectangles in atlas + recs[i].x = rects[i].x + (float)padding; + recs[i].y = rects[i].y + (float)padding; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; + + if (rects[i].was_packed) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < chars[i].image.height; y++) + { + for (int x = 0; x < chars[i].image.width; x++) + { + ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x]; + } + } + } + else TRACELOG(LOG_WARNING, "FONT: Failed to package character (%i)", i); + } + + RL_FREE(rects); + RL_FREE(nodes); + RL_FREE(context); + } + + // TODO: Crop image if required for smaller size + + // Convert image data from GRAYSCALE to GRAY_ALPHA + unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(atlas.width*atlas.height*sizeof(unsigned char)*2); // Two channels + + for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2) + { + dataGrayAlpha[k] = 255; + dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i]; + } + + RL_FREE(atlas.data); + atlas.data = dataGrayAlpha; + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + + *charRecs = recs; + + return atlas; +} +#endif + +// Unload font chars info data (RAM) +void UnloadFontData(CharInfo *chars, int charsCount) +{ + for (int i = 0; i < charsCount; i++) UnloadImage(chars[i].image); + + RL_FREE(chars); +} + +// Unload Font from GPU memory (VRAM) +void UnloadFont(Font font) +{ + // NOTE: Make sure font is not default font (fallback) + if (font.texture.id != GetFontDefault().texture.id) + { + UnloadFontData(font.chars, font.charsCount); + UnloadTexture(font.texture); + RL_FREE(font.recs); + + TRACELOGD("FONT: Unloaded font data from RAM and VRAM"); + } +} + +// Draw current FPS +// NOTE: Uses default font +void DrawFPS(int posX, int posY) +{ + Color color = LIME; // good fps + int fps = GetFPS(); + + if (fps < 30 && fps >= 15) color = ORANGE; // warning FPS + else if (fps < 15) color = RED; // bad FPS + + DrawText(TextFormat("%2i FPS", GetFPS()), posX, posY, 20, color); +} + +// Draw text (using default font) +// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used +// NOTE: chars spacing is proportional to fontSize +void DrawText(const char *text, int posX, int posY, int fontSize, Color color) +{ + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + Vector2 position = { (float)posX, (float)posY }; + + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + DrawTextEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, color); + } +} + +// Draw text using Font +// NOTE: chars spacing is NOT proportional to fontSize +void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) +{ + int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop + + int textOffsetY = 0; // Offset between lines (on line break '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + for (int i = 0; i < length;) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + + if (codepoint == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0.0f; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + DrawTextCodepoint(font, codepoint, (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint); + } + + if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); + else textOffsetX += ((float)font.chars[index].advanceX*scaleFactor + spacing); + } + + i += codepointByteCount; // Move text bytes counter to next codepoint + } +} + +// Draw text using font inside rectangle limits +void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) +{ + DrawTextRecEx(font, text, rec, fontSize, spacing, wordWrap, tint, 0, 0, WHITE, WHITE); +} + +// Draw text using font inside rectangle limits with support for text selection +void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint) +{ + int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop + + int textOffsetY = 0; // Offset between lines (on line break '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + // Word/character wrapping mechanism variables + enum { MEASURE_STATE = 0, DRAW_STATE = 1 }; + int state = wordWrap? MEASURE_STATE : DRAW_STATE; + + int startLine = -1; // Index where to begin drawing (where a line begins) + int endLine = -1; // Index where to stop drawing (where a line ends) + int lastk = -1; // Holds last value of the character position + + for (int i = 0, k = 0; i < length; i++, k++) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + i += (codepointByteCount - 1); + + int glyphWidth = 0; + if (codepoint != '\n') + { + glyphWidth = (font.chars[index].advanceX == 0)? + (int)(font.recs[index].width*scaleFactor + spacing): + (int)(font.chars[index].advanceX*scaleFactor + spacing); + } + + // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container + // We store this info in startLine and endLine, then we change states, draw the text between those two variables + // and change states again and again recursively until the end of the text (or until we get outside of the container). + // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately + // and begin drawing on the next line before we can get outside the container. + if (state == MEASURE_STATE) + { + // TODO: There are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more + // Ref: http://jkorpela.fi/chars/spaces.html + if ((codepoint == ' ') || (codepoint == '\t') || (codepoint == '\n')) endLine = i; + + if ((textOffsetX + glyphWidth + 1) >= rec.width) + { + endLine = (endLine < 1)? i : endLine; + if (i == endLine) endLine -= codepointByteCount; + if ((startLine + codepointByteCount) == endLine) endLine = (i - codepointByteCount); + + state = !state; + } + else if ((i + 1) == length) + { + endLine = i; + + state = !state; + } + else if (codepoint == '\n') state = !state; + + if (state == DRAW_STATE) + { + textOffsetX = 0; + i = startLine; + glyphWidth = 0; + + // Save character position when we switch states + int tmp = lastk; + lastk = k - 1; + k = tmp; + } + } + else + { + if (codepoint == '\n') + { + if (!wordWrap) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + } + else + { + if (!wordWrap && ((textOffsetX + glyphWidth + 1) >= rec.width)) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + } + + // When text overflows rectangle height limit, just stop drawing + if ((textOffsetY + (int)(font.baseSize*scaleFactor)) > rec.height) break; + + // Draw selection background + bool isGlyphSelected = false; + if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength))) + { + DrawRectangleRec((Rectangle){ rec.x + textOffsetX - 1, rec.y + textOffsetY, (float)glyphWidth, (float)font.baseSize*scaleFactor }, selectBackTint); + isGlyphSelected = true; + } + + // Draw current character glyph + if ((codepoint != ' ') && (codepoint != '\t')) + { + DrawTextCodepoint(font, codepoint, (Vector2){ rec.x + textOffsetX, rec.y + textOffsetY }, fontSize, isGlyphSelected? selectTint : tint); + } + } + + if (wordWrap && (i == endLine)) + { + textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetX = 0; + startLine = endLine; + endLine = -1; + glyphWidth = 0; + selectStart += lastk - k; + k = lastk; + + state = !state; + } + } + + textOffsetX += glyphWidth; + } +} + +// Draw one character (codepoint) +void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint) +{ + // Character index position in sprite font + // NOTE: In case a codepoint is not available in the font, index returned points to '?' + int index = GetGlyphIndex(font, codepoint); + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + // Character destination rectangle on screen + // NOTE: We consider charsPadding on drawing + Rectangle dstRec = { position.x + font.chars[index].offsetX*scaleFactor - (float)font.charsPadding*scaleFactor, + position.y + font.chars[index].offsetY*scaleFactor - (float)font.charsPadding*scaleFactor, + (font.recs[index].width + 2.0f*font.charsPadding)*scaleFactor, + (font.recs[index].height + 2.0f*font.charsPadding)*scaleFactor }; + + // Character source rectangle from font texture atlas + // NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects + Rectangle srcRec = { font.recs[index].x - (float)font.charsPadding, font.recs[index].y - (float)font.charsPadding, + font.recs[index].width + 2.0f*font.charsPadding, font.recs[index].height + 2.0f*font.charsPadding }; + + // Draw the character texture on the screen + DrawTexturePro(font.texture, srcRec, dstRec, (Vector2){ 0, 0 }, 0.0f, tint); +} + +// Measure string width for default font +int MeasureText(const char *text, int fontSize) +{ + Vector2 vec = { 0.0f, 0.0f }; + + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing); + } + + return (int)vec.x; +} + +// Measure string size for Font +Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing) +{ + int len = TextLength(text); + int tempLen = 0; // Used to count longer text line num chars + int lenCounter = 0; + + float textWidth = 0.0f; + float tempTextWidth = 0.0f; // Used to count longer text line width + + float textHeight = (float)font.baseSize; + float scaleFactor = fontSize/(float)font.baseSize; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + for (int i = 0; i < len; i++) + { + lenCounter++; + + int next = 0; + letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) next = 1; + i += next - 1; + + if (letter != '\n') + { + if (font.chars[index].advanceX != 0) textWidth += font.chars[index].advanceX; + else textWidth += (font.recs[index].width + font.chars[index].offsetX); + } + else + { + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + lenCounter = 0; + textWidth = 0; + textHeight += ((float)font.baseSize*1.5f); // NOTE: Fixed line spacing of 1.5 lines + } + + if (tempLen < lenCounter) tempLen = lenCounter; + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + Vector2 vec = { 0 }; + vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure + vec.y = textHeight*scaleFactor; + + return vec; +} + +// Returns index position for a unicode character on spritefont +int GetGlyphIndex(Font font, int codepoint) +{ +#ifndef GLYPH_NOTFOUND_CHAR_FALLBACK + #define GLYPH_NOTFOUND_CHAR_FALLBACK 63 // Character used if requested codepoint is not found: '?' +#endif + +// Support charsets with any characters order +#define SUPPORT_UNORDERED_CHARSET +#if defined(SUPPORT_UNORDERED_CHARSET) + int index = GLYPH_NOTFOUND_CHAR_FALLBACK; + + for (int i = 0; i < font.charsCount; i++) + { + if (font.chars[i].value == codepoint) + { + index = i; + break; + } + } + + return index; +#else + return (codepoint - 32); +#endif +} + +//---------------------------------------------------------------------------------- +// Text strings management functions +//---------------------------------------------------------------------------------- +// Get text length in bytes, check for \0 character +unsigned int TextLength(const char *text) +{ + unsigned int length = 0; //strlen(text) + + if (text != NULL) + { + while (*text++) length++; + } + + return length; +} + +// Formatting of text with variables to 'embed' +// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times +const char *TextFormat(const char *text, ...) +{ +#ifndef MAX_TEXTFORMAT_BUFFERS + #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting +#endif + + // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations + static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; + static int index = 0; + + char *currentBuffer = buffers[index]; + memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using + + va_list args; + va_start(args, text); + vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); + va_end(args); + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + + return currentBuffer; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +#if defined(SUPPORT_TEXT_MANIPULATION) +// Copy one string to another, returns bytes copied +int TextCopy(char *dst, const char *src) +{ + int bytes = 0; + + if (dst != NULL) + { + while (*src != '\0') + { + *dst = *src; + dst++; + src++; + + bytes++; + } + + *dst = '\0'; + } + + return bytes; +} + +// Check if two text string are equal +// REQUIRES: strcmp() +bool TextIsEqual(const char *text1, const char *text2) +{ + bool result = false; + + if (strcmp(text1, text2) == 0) result = true; + + return result; +} + +// Get a piece of a text string +const char *TextSubtext(const char *text, int position, int length) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + int textLength = TextLength(text); + + if (position >= textLength) + { + position = textLength - 1; + length = 0; + } + + if (length >= textLength) length = textLength; + + for (int c = 0 ; c < length ; c++) + { + *(buffer + c) = *(text + position); + text++; + } + + *(buffer + length) = '\0'; + + return buffer; +} + +// Replace text string +// REQUIRES: strstr(), strncpy(), strcpy() +// WARNING: Internally allocated memory must be freed by the user (if return != NULL) +char *TextReplace(char *text, const char *replace, const char *by) +{ + // Sanity checks and initialization + if (!text || !replace || !by) return NULL; + + char *result; + + char *insertPoint; // Next insert point + char *temp; // Temp pointer + int replaceLen; // Replace string length of (the string to remove) + int byLen; // Replacement length (the string to replace replace by) + int lastReplacePos; // Distance between replace and end of last replace + int count; // Number of replacements + + replaceLen = TextLength(replace); + if (replaceLen == 0) return NULL; // Empty replace causes infinite loop during count + + byLen = TextLength(by); + + // Count the number of replacements needed + insertPoint = text; + for (count = 0; (temp = strstr(insertPoint, replace)); count++) insertPoint = temp + replaceLen; + + // Allocate returning string and point temp to it + temp = result = RL_MALLOC(TextLength(text) + (byLen - replaceLen)*count + 1); + + if (!result) return NULL; // Memory could not be allocated + + // First time through the loop, all the variable are set correctly from here on, + // temp points to the end of the result string + // insertPoint points to the next occurrence of replace in text + // text points to the remainder of text after "end of replace" + while (count--) + { + insertPoint = strstr(text, replace); + lastReplacePos = (int)(insertPoint - text); + temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; + temp = strcpy(temp, by) + byLen; + text += lastReplacePos + replaceLen; // Move to next "end of replace" + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + strcpy(temp, text); + + return result; +} + +// Insert text in a specific position, moves all text forward +// WARNING: Allocated memory should be manually freed +char *TextInsert(const char *text, const char *insert, int position) +{ + int textLen = TextLength(text); + int insertLen = TextLength(insert); + + char *result = (char *)RL_MALLOC(textLen + insertLen + 1); + + for (int i = 0; i < position; i++) result[i] = text[i]; + for (int i = position; i < insertLen + position; i++) result[i] = insert[i]; + for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i]; + + result[textLen + insertLen] = '\0'; // Make sure text string is valid! + + return result; +} + +// Join text strings with delimiter +// REQUIRES: memset(), memcpy() +const char *TextJoin(const char **textList, int count, const char *delimiter) +{ + static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(text, 0, MAX_TEXT_BUFFER_LENGTH); + char *textPtr = text; + + int totalLength = 0; + int delimiterLen = TextLength(delimiter); + + for (int i = 0; i < count; i++) + { + int textLength = TextLength(textList[i]); + + // Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH + if ((totalLength + textLength) < MAX_TEXT_BUFFER_LENGTH) + { + memcpy(textPtr, textList[i], textLength); + totalLength += textLength; + textPtr += textLength; + + if ((delimiterLen > 0) && (i < (count - 1))) + { + memcpy(textPtr, delimiter, delimiterLen); + totalLength += delimiterLen; + textPtr += delimiterLen; + } + } + } + + return text; +} + +// Split string into multiple strings +// REQUIRES: memset() +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by MAX_TEXTSPLIT_COUNT + // 2. Maximum size of text to split is MAX_TEXT_BUFFER_LENGTH + + static const char *result[MAX_TEXTSPLIT_COUNT] = { NULL }; + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == MAX_TEXTSPLIT_COUNT) break; + } + } + } + + *count = counter; + return result; +} + +// Append text at specific position and move cursor! +// REQUIRES: strcpy() +void TextAppend(char *text, const char *append, int *position) +{ + strcpy(text + *position, append); + *position += TextLength(append); +} + +// Find first text occurrence within a string +// REQUIRES: strstr() +int TextFindIndex(const char *text, const char *find) +{ + int position = -1; + + char *ptr = strstr(text, find); + + if (ptr != NULL) position = (int)(ptr - text); + + return position; +} + +// Get upper case version of provided string +// REQUIRES: toupper() +const char *TextToUpper(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + if (text[i] != '\0') + { + buffer[i] = (char)toupper(text[i]); + //if ((text[i] >= 'a') && (text[i] <= 'z')) buffer[i] = text[i] - 32; + + // TODO: Support Utf8 diacritics! + //if ((text[i] >= 'à') && (text[i] <= 'ý')) buffer[i] = text[i] - 32; + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get lower case version of provided string +// REQUIRES: tolower() +const char *TextToLower(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + if (text[i] != '\0') + { + buffer[i] = (char)tolower(text[i]); + //if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32; + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Get Pascal case notation version of provided string +// REQUIRES: toupper() +const char *TextToPascal(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + buffer[0] = (char)toupper(text[0]); + + for (int i = 1, j = 1; i < MAX_TEXT_BUFFER_LENGTH; i++, j++) + { + if (text[j] != '\0') + { + if (text[j] != '_') buffer[i] = text[j]; + else + { + j++; + buffer[i] = (char)toupper(text[j]); + } + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + +// Encode text codepoint into utf8 text +// REQUIRES: memcpy() +// WARNING: Allocated memory should be manually freed +char *TextToUtf8(int *codepoints, int length) +{ + // We allocate enough memory fo fit all possible codepoints + // NOTE: 5 bytes for every codepoint should be enough + char *text = (char *)RL_CALLOC(length*5, 1); + const char *utf8 = NULL; + int size = 0; + + for (int i = 0, bytes = 0; i < length; i++) + { + utf8 = CodepointToUtf8(codepoints[i], &bytes); + memcpy(text + size, utf8, bytes); + size += bytes; + } + + // Resize memory to text length + string NULL terminator + void *ptr = RL_REALLOC(text, size + 1); + + if (ptr != NULL) text = (char *)ptr; + + return text; +} + +// Encode codepoint into utf8 text (char array length returned as parameter) +RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength) +{ + static char utf8[6] = { 0 }; + int length = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + length = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + length = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + length = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + length = 4; + } + + *byteLength = length; + + return utf8; +} + +// Get all codepoints in a string, codepoints count returned by parameters +// REQUIRES: memset() +int *GetCodepoints(const char *text, int *count) +{ + static int codepoints[MAX_TEXT_UNICODE_CHARS] = { 0 }; + memset(codepoints, 0, MAX_TEXT_UNICODE_CHARS*sizeof(int)); + + int bytesProcessed = 0; + int textLength = TextLength(text); + int codepointsCount = 0; + + for (int i = 0; i < textLength; codepointsCount++) + { + codepoints[codepointsCount] = GetNextCodepoint(text + i, &bytesProcessed); + i += bytesProcessed; + } + + *count = codepointsCount; + + return codepoints; +} + +// Returns total number of characters(codepoints) in a UTF8 encoded text, until '\0' is found +// NOTE: If an invalid UTF8 sequence is encountered a '?'(0x3f) codepoint is counted instead +int GetCodepointsCount(const char *text) +{ + unsigned int len = 0; + char *ptr = (char *)&text[0]; + + while (*ptr != '\0') + { + int next = 0; + int letter = GetNextCodepoint(ptr, &next); + + if (letter == 0x3f) ptr += 1; + else ptr += next; + + len++; + } + + return len; +} +#endif // SUPPORT_TEXT_MANIPULATION + +// Returns next codepoint in a UTF8 encoded text, scanning until '\0' is found +// When a invalid UTF8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +// TODO: Optimize this code for speed!! +int GetNextCodepoint(const char *text, int *bytesProcessed) +{ +/* + UTF8 specs from https://www.ietf.org/rfc/rfc3629.txt + + Char. number range | UTF-8 octet sequence + (hexadecimal) | (binary) + --------------------+--------------------------------------------- + 0000 0000-0000 007F | 0xxxxxxx + 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx +*/ + // NOTE: on decode errors we return as soon as possible + + int code = 0x3f; // Codepoint (defaults to '?') + int octet = (unsigned char)(text[0]); // The first UTF8 octet + *bytesProcessed = 1; + + if (octet <= 0x7f) + { + // Only one octet (ASCII range x00-7F) + code = text[0]; + } + else if ((octet & 0xe0) == 0xc0) + { + // Two octets + // [0]xC2-DF [1]UTF8-tail(x80-BF) + unsigned char octet1 = text[1]; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if ((octet >= 0xc2) && (octet <= 0xdf)) + { + code = ((octet & 0x1f) << 6) | (octet1 & 0x3f); + *bytesProcessed = 2; + } + } + else if ((octet & 0xf0) == 0xe0) + { + // Three octets + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + /* + [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) + [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF) + [0]xED [1]x80-9F [2]UTF8-tail(x80-BF) + [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) + */ + + if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || + ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *bytesProcessed = 2; return code; } + + if ((octet >= 0xe0) && (0 <= 0xef)) + { + code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); + *bytesProcessed = 3; + } + } + else if ((octet & 0xf8) == 0xf0) + { + // Four octets + if (octet > 0xf4) return code; + + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + unsigned char octet3 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + octet3 = text[3]; + + if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *bytesProcessed = 4; return code; } // Unexpected sequence + + /* + [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail + [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail + [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail + */ + + if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) || + ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if (octet >= 0xf0) + { + code = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f); + *bytesProcessed = 4; + } + } + + if (code > 0x10ffff) code = 0x3f; // Codepoints after U+10ffff are invalid + + return code; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_FNT) + +// Read a line from memory +// REQUIRES: memcpy() +// NOTE: Returns the number of bytes read +static int GetLine(const char *origin, char *buffer, int maxLength) +{ + int count = 0; + for (; count < maxLength; count++) if (origin[count] == '\n') break; + memcpy(buffer, origin, count); + return count; +} + +// Load a BMFont file (AngelCode font file) +// REQUIRES: strstr(), sscanf(), strrchr(), memcpy() +static Font LoadBMFont(const char *fileName) +{ + #define MAX_BUFFER_SIZE 256 + + Font font = { 0 }; + + char buffer[MAX_BUFFER_SIZE] = { 0 }; + char *searchPoint = NULL; + + int fontSize = 0; + int charsCount = 0; + + int imWidth = 0; + int imHeight = 0; + char imFileName[129]; + + int base = 0; // Useless data + + char *fileText = LoadFileText(fileName); + + if (fileText == NULL) return font; + + char *fileTextPtr = fileText; + + // NOTE: We skip first line, it contains no useful information + int lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + fileTextPtr += (lineBytes + 1); + + // Read line data + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "lineHeight"); + sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i", &fontSize, &base, &imWidth, &imHeight); + fileTextPtr += (lineBytes + 1); + + TRACELOGD("FONT: [%s] Loaded font info:", fileName); + TRACELOGD(" > Base size: %i", fontSize); + TRACELOGD(" > Texture scale: %ix%i", imWidth, imHeight); + + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "file"); + sscanf(searchPoint, "file=\"%128[^\"]\"", imFileName); + fileTextPtr += (lineBytes + 1); + + TRACELOGD(" > Texture filename: %s", imFileName); + + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "count"); + sscanf(searchPoint, "count=%i", &charsCount); + fileTextPtr += (lineBytes + 1); + + TRACELOGD(" > Chars count: %i", charsCount); + + // Compose correct path using route of .fnt file (fileName) and imFileName + char *imPath = NULL; + char *lastSlash = NULL; + + lastSlash = strrchr(fileName, '/'); + if (lastSlash == NULL) lastSlash = strrchr(fileName, '\\'); + + if (lastSlash != NULL) + { + // NOTE: We need some extra space to avoid memory corruption on next allocations! + imPath = RL_CALLOC(TextLength(fileName) - TextLength(lastSlash) + TextLength(imFileName) + 4, 1); + memcpy(imPath, fileName, TextLength(fileName) - TextLength(lastSlash) + 1); + memcpy(imPath + TextLength(fileName) - TextLength(lastSlash) + 1, imFileName, TextLength(imFileName)); + } + else imPath = imFileName; + + TRACELOGD(" > Image loading path: %s", imPath); + + Image imFont = LoadImage(imPath); + + if (imFont.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel + Image imFontAlpha = { + .data = calloc(imFont.width*imFont.height, 2), + .width = imFont.width, + .height = imFont.height, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, + .mipmaps = 1 + }; + + for (int p = 0, i = 0; p < (imFont.width*imFont.height*2); p += 2, i++) + { + ((unsigned char *)(imFontAlpha.data))[p] = 0xff; + ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFont.data)[i]; + } + + UnloadImage(imFont); + imFont = imFontAlpha; + } + + font.texture = LoadTextureFromImage(imFont); + + if (lastSlash != NULL) RL_FREE(imPath); + + // Fill font characters info data + font.baseSize = fontSize; + font.charsCount = charsCount; + font.charsPadding = 0; + font.chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); + font.recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); + + int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX; + + for (int i = 0; i < charsCount; i++) + { + lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i", + &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX); + fileTextPtr += (lineBytes + 1); + + // Get character rectangle in the font atlas texture + font.recs[i] = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight }; + + // Save data properly in sprite font + font.chars[i].value = charId; + font.chars[i].offsetX = charOffsetX; + font.chars[i].offsetY = charOffsetY; + font.chars[i].advanceX = charAdvanceX; + + // Fill character image data from imFont data + font.chars[i].image = ImageFromImage(imFont, font.recs[i]); + } + + UnloadImage(imFont); + RL_FREE(fileText); + + if (font.texture.id == 0) + { + UnloadFont(font); + font = GetFontDefault(); + TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load texture, reverted to default font", fileName); + } + else TRACELOG(LOG_INFO, "FONT: [%s] Font loaded successfully", fileName); + + return font; +} +#endif diff --git a/raylib_pi4_test/textures.c b/raylib_pi4_test/textures.c new file mode 100644 index 0000000..de0d76b --- /dev/null +++ b/raylib_pi4_test/textures.c @@ -0,0 +1,4588 @@ +/********************************************************************************************** +* +* raylib.textures - Basic functions to load and draw Textures (2d) +* +* CONFIGURATION: +* +* #define SUPPORT_FILEFORMAT_BMP +* #define SUPPORT_FILEFORMAT_PNG +* #define SUPPORT_FILEFORMAT_TGA +* #define SUPPORT_FILEFORMAT_JPG +* #define SUPPORT_FILEFORMAT_GIF +* #define SUPPORT_FILEFORMAT_PSD +* #define SUPPORT_FILEFORMAT_PIC +* #define SUPPORT_FILEFORMAT_HDR +* #define SUPPORT_FILEFORMAT_DDS +* #define SUPPORT_FILEFORMAT_PKM +* #define SUPPORT_FILEFORMAT_KTX +* #define SUPPORT_FILEFORMAT_PVR +* #define SUPPORT_FILEFORMAT_ASTC +* Select desired fileformats to be supported for image data loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define SUPPORT_IMAGE_EXPORT +* Support image export in multiple file formats +* +* #define SUPPORT_IMAGE_MANIPULATION +* Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... +* If not defined only three image editing functions supported: ImageFormat(), ImageAlphaMask(), ImageToPOT() +* +* #define SUPPORT_IMAGE_GENERATION +* Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) +* +* DEPENDENCIES: +* stb_image - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC) +* NOTE: stb_image has been slightly modified to support Android platform. +* stb_image_resize - Multiple image resize algorythms +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include // Required for: malloc(), free() +#include // Required for: strlen() [Used in ImageTextEx()] +#include // Required for: fabsf() + +#include "utils.h" // Required for: fopen() Android mapping + +#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2 + // Required for: rlLoadTexture() rlUnloadTexture(), + // rlGenerateMipmaps(), some funcs for DrawTexturePro() + +// Support only desired texture formats on stb_image +#if !defined(SUPPORT_FILEFORMAT_BMP) + #define STBI_NO_BMP +#endif +#if !defined(SUPPORT_FILEFORMAT_PNG) + #define STBI_NO_PNG +#endif +#if !defined(SUPPORT_FILEFORMAT_TGA) + #define STBI_NO_TGA +#endif +#if !defined(SUPPORT_FILEFORMAT_JPG) + #define STBI_NO_JPEG // Image format .jpg and .jpeg +#endif +#if !defined(SUPPORT_FILEFORMAT_PSD) + #define STBI_NO_PSD +#endif +#if !defined(SUPPORT_FILEFORMAT_GIF) + #define STBI_NO_GIF +#endif +#if !defined(SUPPORT_FILEFORMAT_PIC) + #define STBI_NO_PIC +#endif +#if !defined(SUPPORT_FILEFORMAT_HDR) + #define STBI_NO_HDR +#endif + +// Image fileformats not supported by default +#define STBI_NO_PIC +#define STBI_NO_PNM // Image format .ppm and .pgm + +#if (defined(SUPPORT_FILEFORMAT_BMP) || \ + defined(SUPPORT_FILEFORMAT_PNG) || \ + defined(SUPPORT_FILEFORMAT_TGA) || \ + defined(SUPPORT_FILEFORMAT_JPG) || \ + defined(SUPPORT_FILEFORMAT_PSD) || \ + defined(SUPPORT_FILEFORMAT_GIF) || \ + defined(SUPPORT_FILEFORMAT_PIC) || \ + defined(SUPPORT_FILEFORMAT_HDR)) + + #define STBI_MALLOC RL_MALLOC + #define STBI_FREE RL_FREE + #define STBI_REALLOC RL_REALLOC + + #define STB_IMAGE_IMPLEMENTATION + #include "external/stb_image.h" // Required for: stbi_load_from_file() + // NOTE: Used to read image data (multiple formats support) +#endif + +#if (defined(SUPPORT_IMAGE_EXPORT) || defined(SUPPORT_COMPRESSION_API)) + #define STBIW_MALLOC RL_MALLOC + #define STBIW_FREE RL_FREE + #define STBIW_REALLOC RL_REALLOC + + #define STB_IMAGE_WRITE_IMPLEMENTATION + #include "external/stb_image_write.h" // Required for: stbi_write_*() +#endif + +#if defined(SUPPORT_IMAGE_MANIPULATION) + #define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size)) + #define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr)) + + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include "external/stb_image_resize.h" // Required for: stbir_resize_uint8() [ImageResize()] +#endif + +#if defined(SUPPORT_IMAGE_GENERATION) + #define STB_PERLIN_IMPLEMENTATION + #include "external/stb_perlin.h" // Required for: stb_perlin_fbm_noise3 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD + #define PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// It's lonely here... + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by text) +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_DDS) +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize); // Load DDS file data +#endif +#if defined(SUPPORT_FILEFORMAT_PKM) +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize); // Load PKM file data +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize); // Load KTX file data +static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file +#endif +#if defined(SUPPORT_FILEFORMAT_PVR) +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize); // Load PVR file data +#endif +#if defined(SUPPORT_FILEFORMAT_ASTC) +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize); // Load ASTC file data +#endif +static Vector4 *LoadImageDataNormalized(Image image); // Load pixel data from image as Vector4 array (float normalized) + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Load image from file into CPU memory (RAM) +Image LoadImage(const char *fileName) +{ + Image image = { 0 }; + +#if defined(SUPPORT_FILEFORMAT_PNG) || \ + defined(SUPPORT_FILEFORMAT_BMP) || \ + defined(SUPPORT_FILEFORMAT_TGA) || \ + defined(SUPPORT_FILEFORMAT_JPG) || \ + defined(SUPPORT_FILEFORMAT_GIF) || \ + defined(SUPPORT_FILEFORMAT_PIC) || \ + defined(SUPPORT_FILEFORMAT_HDR) || \ + defined(SUPPORT_FILEFORMAT_PSD) +#define STBI_REQUIRED +#endif + + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading image from memory data + image = LoadImageFromMemory(GetFileExtension(fileName), fileData, fileSize); + + if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: [%s] Data loaded successfully (%ix%i)", fileName, image.width, image.height); + else TRACELOG(LOG_WARNING, "IMAGE: [%s] Failed to load data", fileName); + + RL_FREE(fileData); + } + + return image; +} + +// Load an image from RAW file data +Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize) +{ + Image image = { 0 }; + + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + unsigned char *dataPtr = fileData; + unsigned int size = GetPixelDataSize(width, height, format); + + if (headerSize > 0) dataPtr += headerSize; + + image.data = RL_MALLOC(size); // Allocate required memory in bytes + memcpy(image.data, dataPtr, size); // Copy required data to image + image.width = width; + image.height = height; + image.mipmaps = 1; + image.format = format; + + RL_FREE(fileData); + } + + return image; +} + +// Load animated image data +// - Image.data buffer includes all frames: [image#0][image#1][image#2][...] +// - Number of frames is returned through 'frames' parameter +// - All frames are returned in RGBA format +// - Frames delay data is discarded +Image LoadImageAnim(const char *fileName, int *frames) +{ + Image image = { 0 }; + int framesCount = 1; + +#if defined(SUPPORT_FILEFORMAT_GIF) + if (IsFileExtension(fileName, ".gif")) + { + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + int comp = 0; + int **delays = NULL; + image.data = stbi_load_gif_from_memory(fileData, dataSize, delays, &image.width, &image.height, &framesCount, &comp, 4); + + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + RL_FREE(fileData); + RL_FREE(delays); // NOTE: Frames delays are discarded + } + } +#else + if (false) { } +#endif + else image = LoadImage(fileName); + + // TODO: Support APNG animated images? + + *frames = framesCount; + return image; +} + +// Load image from memory buffer, fileType refers to extension: i.e. ".png" +Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Image image = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + +#if defined(SUPPORT_FILEFORMAT_PNG) + if ((TextIsEqual(fileExtLower, ".png")) +#else + if ((false) +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) + || (TextIsEqual(fileExtLower, ".bmp")) +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) + || (TextIsEqual(fileExtLower, ".tga")) +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) + || (TextIsEqual(fileExtLower, ".jpg") || + TextIsEqual(fileExtLower, ".jpeg")) +#endif +#if defined(SUPPORT_FILEFORMAT_GIF) + || (TextIsEqual(fileExtLower, ".gif")) +#endif +#if defined(SUPPORT_FILEFORMAT_PIC) + || (TextIsEqual(fileExtLower, ".pic")) +#endif +#if defined(SUPPORT_FILEFORMAT_PSD) + || (TextIsEqual(fileExtLower, ".psd")) +#endif + ) + { +#if defined(STBI_REQUIRED) + // NOTE: Using stb_image to load images (Supports multiple image formats) + + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (comp == 2) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } +#endif + } +#if defined(SUPPORT_FILEFORMAT_HDR) + else if (TextIsEqual(fileExtLower, ".hdr")) + { +#if defined(STBI_REQUIRED) + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_R32; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; + else + { + TRACELOG(LOG_WARNING, "IMAGE: HDR file format not supported"); + UnloadImage(image); + } + } +#endif + } +#endif +#if defined(SUPPORT_FILEFORMAT_DDS) + else if (TextIsEqual(fileExtLower, ".dds")) image = LoadDDS(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_PKM) + else if (TextIsEqual(fileExtLower, ".pkm")) image = LoadPKM(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) + else if (TextIsEqual(fileExtLower, ".ktx")) image = LoadKTX(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_PVR) + else if (TextIsEqual(fileExtLower, ".pvr")) image = LoadPVR(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_ASTC) + else if (TextIsEqual(fileExtLower, ".astc")) image = LoadASTC(fileData, dataSize); +#endif + else TRACELOG(LOG_WARNING, "IMAGE: File format not supported"); + + return image; +} + +// Unload image from CPU memory (RAM) +void UnloadImage(Image image) +{ + RL_FREE(image.data); +} + +// Export image data to file +// NOTE: File format depends on fileName extension +bool ExportImage(Image image, const char *fileName) +{ + int success = 0; + +#if defined(SUPPORT_IMAGE_EXPORT) + int channels = 4; + bool allocatedData = false; + unsigned char *imgData = (unsigned char *)image.data; + + if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; + else + { + // NOTE: Getting Color array as RGBA unsigned char values + imgData = (unsigned char *)LoadImageColors(image); + allocatedData = true; + } + +#if defined(SUPPORT_FILEFORMAT_PNG) + if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, channels, imgData, image.width*channels); +#else + if (false) {} +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) + else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, channels, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) + else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, channels, imgData); +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) + else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) + else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw pixel data (without header) + // NOTE: It's up to the user to track image parameters + success = SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); + } + + if (allocatedData) RL_FREE(imgData); +#endif // SUPPORT_IMAGE_EXPORT + + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); + + return success; +} + +// Export image as code file (.h) defining an array of bytes +bool ExportImageAsCode(Image image, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + int dataSize = GetPixelDataSize(image.width, image.height, image.format); + + // NOTE: Text data buffer size is estimated considering image data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(6*dataSize + 2000, sizeof(char)); + + int bytesCount = 0; + bytesCount += sprintf(txtData + bytesCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + bytesCount += sprintf(txtData + bytesCount, "// feedback and support: ray[at]raylib.com //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "// Copyright (c) 2020 Ramon Santamaria (@raysan5) //\n"); + bytesCount += sprintf(txtData + bytesCount, "// //\n"); + bytesCount += sprintf(txtData + bytesCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + + // Add image information + bytesCount += sprintf(txtData + bytesCount, "// Image data information\n"); + bytesCount += sprintf(txtData + bytesCount, "#define %s_WIDTH %i\n", varFileName, image.width); + bytesCount += sprintf(txtData + bytesCount, "#define %s_HEIGHT %i\n", varFileName, image.height); + bytesCount += sprintf(txtData + bytesCount, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); + + bytesCount += sprintf(txtData + bytesCount, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) bytesCount += sprintf(txtData + bytesCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); + bytesCount += sprintf(txtData + bytesCount, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + return success; +} + +//------------------------------------------------------------------------------------ +// Image generation functions +//------------------------------------------------------------------------------------ +// Generate image: plain color +Image GenImageColor(int width, int height, Color color) +{ + Color *pixels = (Color *)RL_CALLOC(width*height, sizeof(Color)); + + for (int i = 0; i < width*height; i++) pixels[i] = color; + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +#if defined(SUPPORT_IMAGE_GENERATION) +// Generate image: vertical gradient +Image GenImageGradientV(int width, int height, Color top, Color bottom) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int j = 0; j < height; j++) + { + float factor = (float)j/(float)height; + for (int i = 0; i < width; i++) + { + pixels[j*width + i].r = (int)((float)bottom.r*factor + (float)top.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)bottom.g*factor + (float)top.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)bottom.b*factor + (float)top.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)bottom.a*factor + (float)top.a*(1.f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: horizontal gradient +Image GenImageGradientH(int width, int height, Color left, Color right) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int i = 0; i < width; i++) + { + float factor = (float)i/(float)width; + for (int j = 0; j < height; j++) + { + pixels[j*width + i].r = (int)((float)right.r*factor + (float)left.r*(1.f - factor)); + pixels[j*width + i].g = (int)((float)right.g*factor + (float)left.g*(1.f - factor)); + pixels[j*width + i].b = (int)((float)right.b*factor + (float)left.b*(1.f - factor)); + pixels[j*width + i].a = (int)((float)right.a*factor + (float)left.a*(1.f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: radial gradient +Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; + + float centerX = (float)width/2.0f; + float centerY = (float)height/2.0f; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float dist = hypotf((float)x - centerX, (float)y - centerY); + float factor = (dist - radius*density)/(radius*(1.0f - density)); + + factor = (float)fmax(factor, 0.0f); + factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check + + pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); + pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); + pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); + pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: checked +Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; + else pixels[y*width + x] = col2; + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: white noise +Image GenImageWhiteNoise(int width, int height, float factor) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int i = 0; i < width*height; i++) + { + if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; + else pixels[i] = BLACK; + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: perlin noise +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float nx = (float)(x + offsetX)*scale/(float)width; + float ny = (float)(y + offsetY)*scale/(float)height; + + // Typical values to start playing with: + // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) + // gain = 0.5 -- relative weighting applied to each successive octave + // octaves = 6 -- number of "octaves" of noise3() to sum + + // NOTE: We need to translate the data from [-1..1] to [0..1] + float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f)/2.0f; + + int intensity = (int)(p*255.0f); + pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} + +// Generate image: cellular algorithm. Bigger tileSize means bigger cells +Image GenImageCellular(int width, int height, int tileSize) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + int seedsPerRow = width/tileSize; + int seedsPerCol = height/tileSize; + int seedsCount = seedsPerRow*seedsPerCol; + + Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); + + for (int i = 0; i < seedsCount; i++) + { + int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + seeds[i] = (Vector2){ (float)x, (float)y}; + } + + for (int y = 0; y < height; y++) + { + int tileY = y/tileSize; + + for (int x = 0; x < width; x++) + { + int tileX = x/tileSize; + + float minDistance = (float)strtod("Inf", NULL); + + // Check all adjacent tiles + for (int i = -1; i < 2; i++) + { + if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; + + for (int j = -1; j < 2; j++) + { + if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; + + Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; + + float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); + minDistance = (float)fmin(minDistance, dist); + } + } + + // I made this up but it seems to give good results at all tile sizes + int intensity = (int)(minDistance*256.0f/tileSize); + if (intensity > 255) intensity = 255; + + pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + } + } + + RL_FREE(seeds); + + Image image = { + .data = pixels, + .width = width, + .height = height, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + return image; +} +#endif // SUPPORT_IMAGE_GENERATION + +//------------------------------------------------------------------------------------ +// Image manipulation functions +//------------------------------------------------------------------------------------ +// Copy an image to a new image +Image ImageCopy(Image image) +{ + Image newImage = { 0 }; + + int width = image.width; + int height = image.height; + int size = 0; + + for (int i = 0; i < image.mipmaps; i++) + { + size += GetPixelDataSize(width, height, image.format); + + width /= 2; + height /= 2; + + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; + } + + newImage.data = RL_MALLOC(size); + + if (newImage.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newImage.data, image.data, size); + + newImage.width = image.width; + newImage.height = image.height; + newImage.mipmaps = image.mipmaps; + newImage.format = image.format; + } + + return newImage; +} + +// Create an image from another image piece +Image ImageFromImage(Image image, Rectangle rec) +{ + Image result = { 0 }; + + int bytesPerPixel = GetPixelDataSize(1, 1, image.format); + + // TODO: Check rec is valid? + + result.width = (int)rec.width; + result.height = (int)rec.height; + result.data = RL_CALLOC((int)(rec.width*rec.height)*bytesPerPixel, 1); + result.format = image.format; + result.mipmaps = 1; + + for (int y = 0; y < rec.height; y++) + { + memcpy(((unsigned char *)result.data) + y*(int)rec.width*bytesPerPixel, ((unsigned char *)image.data) + ((y + (int)rec.y)*image.width + (int)rec.x)*bytesPerPixel, (int)rec.width*bytesPerPixel); + } + + return result; +} + +// Crop an image to area defined by a rectangle +// NOTE: Security checks are performed in case rectangle goes out of bounds +void ImageCrop(Image *image, Rectangle crop) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Security checks to validate crop rectangle + if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } + if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } + if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; + if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; + if ((crop.x > image->width) || (crop.y > image->height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); + return; + } + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + + unsigned char *croppedData = (unsigned char *)RL_MALLOC((int)(crop.width*crop.height)*bytesPerPixel); + + // OPTION 1: Move cropped data line-by-line + for (int y = (int)crop.y, offsetSize = 0; y < (int)(crop.y + crop.height); y++) + { + memcpy(croppedData + offsetSize, ((unsigned char *)image->data) + (y*image->width + (int)crop.x)*bytesPerPixel, (int)crop.width*bytesPerPixel); + offsetSize += ((int)crop.width*bytesPerPixel); + } + + /* + // OPTION 2: Move cropped data pixel-by-pixel or byte-by-byte + for (int y = (int)crop.y; y < (int)(crop.y + crop.height); y++) + { + for (int x = (int)crop.x; x < (int)(crop.x + crop.width); x++) + { + //memcpy(croppedData + ((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) croppedData[((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; + } + } + */ + + RL_FREE(image->data); + image->data = croppedData; + image->width = (int)crop.width; + image->height = (int)crop.height; + } +} + +// Convert image data to desired format +void ImageFormat(Image *image, int newFormat) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if ((newFormat != 0) && (image->format != newFormat)) + { + if ((image->format < PIXELFORMAT_COMPRESSED_DXT1_RGB) && (newFormat < PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + Vector4 *pixels = LoadImageDataNormalized(*image); // Supports 8 to 32 bit per channel + + RL_FREE(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... + image->data = NULL; + image->format = newFormat; + + int k = 0; + + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((unsigned char *)image->data)[i] = (unsigned char)((pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)*255.0f); + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char)); + + for (int i = 0; i < image->width*image->height*2; i += 2, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)((pixels[k].x*0.299f + (float)pixels[k].y*0.587f + (float)pixels[k].z*0.114f)*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f); + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*31.0f)); + g = (unsigned char)(round(pixels[i].y*63.0f)); + b = (unsigned char)(round(pixels[i].z*31.0f)); + + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char)); + + for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); + ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*31.0f)); + g = (unsigned char)(round(pixels[i].y*31.0f)); + b = (unsigned char)(round(pixels[i].z*31.0f)); + a = (pixels[i].w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; + + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*15.0f)); + g = (unsigned char)(round(pixels[i].y*15.0f)); + b = (unsigned char)(round(pixels[i].z*15.0f)); + a = (unsigned char)(round(pixels[i].w*15.0f)); + + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char)); + + for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); + ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); + ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit + + image->data = (float *)RL_MALLOC(image->width*image->height*sizeof(float)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float)); + + for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) + { + ((float *)image->data)[i] = pixels[k].x; + ((float *)image->data)[i + 1] = pixels[k].y; + ((float *)image->data)[i + 2] = pixels[k].z; + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float)); + + for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) + { + ((float *)image->data)[i] = pixels[k].x; + ((float *)image->data)[i + 1] = pixels[k].y; + ((float *)image->data)[i + 2] = pixels[k].z; + ((float *)image->data)[i + 3] = pixels[k].w; + } + } break; + default: break; + } + + RL_FREE(pixels); + pixels = NULL; + + // In case original image had mipmaps, generate mipmaps for formated image + // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost + if (image->mipmaps > 1) + { + image->mipmaps = 1; + #if defined(SUPPORT_IMAGE_MANIPULATION) + if (image->data != NULL) ImageMipmaps(image); + #endif + } + } + else TRACELOG(LOG_WARNING, "IMAGE: Data format is compressed, can not be converted"); + } +} + +// Convert image to POT (power-of-two) +// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) +void ImageToPOT(Image *image, Color fill) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Calculate next power-of-two values + // NOTE: Just add the required amount of pixels at the right and bottom sides of image... + int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); + int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); + + // Check if POT texture generation is required (if texture is not already POT) + if ((potWidth != image->width) || (potHeight != image->height)) ImageResizeCanvas(image, potWidth, potHeight, 0, 0, fill); +} + +#if defined(SUPPORT_IMAGE_MANIPULATION) +// Create an image from text (default font) +Image ImageText(const char *text, int fontSize, Color color) +{ + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); + + return imText; +} + +// Create an image from text (custom sprite font) +Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) +{ + int length = (int)strlen(text); + + int textOffsetX = 0; // Image drawing position X + int textOffsetY = 0; // Offset between lines (on line break '\n') + + // NOTE: Text image is generated at font base size, later scaled to desired font size + Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); + + // Create image to store text + Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + + for (int i = 0; i < length; i++) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetNextCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + + if (codepoint == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (font.baseSize + font.baseSize/2); + textOffsetX = 0; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + Rectangle rec = { (float)(textOffsetX + font.chars[index].offsetX), (float)(textOffsetY + font.chars[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height }; + ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, (float)font.chars[index].image.width, (float)font.chars[index].image.height }, rec, tint); + } + + if (font.chars[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); + else textOffsetX += font.chars[index].advanceX + (int)spacing; + } + + i += (codepointByteCount - 1); // Move text bytes counter to next codepoint + } + + // Scale image depending on text size + if (fontSize > imSize.y) + { + float scaleFactor = fontSize/imSize.y; + TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); + + // Using nearest-neighbor scaling algorithm for default font + if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + } + + return imText; +} + +// Crop image depending on alpha value +// NOTE: Threshold is defined as a percentatge: 0.0f -> 1.0f +void ImageAlphaCrop(Image *image, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Rectangle crop = GetImageAlphaBorder(*image, threshold); + + // Crop if rectangle is valid + if (((int)crop.width != 0) && ((int)crop.height != 0)) ImageCrop(image, crop); +} + +// Clear alpha channel to desired color +// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f +void ImageAlphaClear(Image *image, Color color, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 1; i < image->width*image->height*2; i += 2) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 1] = color.r; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned char thresholdValue = ((threshold < 0.5f)? 0 : 1); + + unsigned char r = (unsigned char)(round((float)color.r*31.0f)); + unsigned char g = (unsigned char)(round((float)color.g*31.0f)); + unsigned char b = (unsigned char)(round((float)color.b*31.0f)); + unsigned char a = (color.a < 128)? 0 : 1; + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0b0000000000000001) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned char thresholdValue = (unsigned char)(threshold*15.0f); + + unsigned char r = (unsigned char)(round((float)color.r*15.0f)); + unsigned char g = (unsigned char)(round((float)color.g*15.0f)); + unsigned char b = (unsigned char)(round((float)color.b*15.0f)); + unsigned char a = (unsigned char)(round((float)color.a*15.0f)); + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0x000f) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 3] = color.r; + ((unsigned char *)image->data)[i - 2] = color.g; + ((unsigned char *)image->data)[i - 1] = color.b; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((float *)image->data)[i] <= threshold) + { + ((float *)image->data)[i - 3] = (float)color.r/255.0f; + ((float *)image->data)[i - 2] = (float)color.g/255.0f; + ((float *)image->data)[i - 1] = (float)color.b/255.0f; + ((float *)image->data)[i] = (float)color.a/255.0f; + } + } + } break; + default: break; + } + } +} + +// Apply alpha mask to image +// NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit) +// NOTE 2: alphaMask should be same size as image +void ImageAlphaMask(Image *image, Image alphaMask) +{ + if ((image->width != alphaMask.width) || (image->height != alphaMask.height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); + } + else if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); + } + else + { + // Force mask to be Grayscale + Image mask = ImageCopy(alphaMask); + if (mask.format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); + + // In case image is only grayscale, we just add alpha channel + if (image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) + { + data[k] = ((unsigned char *)image->data)[i]; + data[k + 1] = ((unsigned char *)mask.data)[i]; + } + + RL_FREE(image->data); + image->data = data; + image->format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + } + else + { + // Convert image to RGBA + if (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) + { + ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i]; + } + } + + UnloadImage(mask); + } +} + +// Premultiply alpha channel +void ImageAlphaPremultiply(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + float alpha = 0.0f; + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + if (pixels[i].a == 0) + { + pixels[i].r = 0; + pixels[i].g = 0; + pixels[i].b = 0; + } + else if (pixels[i].a < 255) + { + alpha = (float)pixels[i].a/255.0f; + pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); + pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); + pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); + } + } + + RL_FREE(image->data); + + int format = image->format; + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Resize and image to new size +// NOTE: Uses stb default scaling filters (both bicubic): +// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) +void ImageResize(Image *image, int newWidth, int newHeight) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + bool fastPath = true; + if ((image->format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) && (image->format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) fastPath = true; + + if (fastPath) + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *output = RL_MALLOC(newWidth*newHeight*bytesPerPixel); + + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 1); break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 2); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 3); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 4); break; + default: break; + } + + RL_FREE(image->data); + image->data = output; + image->width = newWidth; + image->height = newHeight; + } + else + { + // Get data as Color pixels array to work with it + Color *pixels = LoadImageColors(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); + + // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem... + stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4); + + int format = image->format; + + UnloadImageColors(pixels); + RL_FREE(image->data); + + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); // Reformat 32bit RGBA image to original format + } +} + +// Resize and image to new size using Nearest-Neighbor scaling algorithm +void ImageResizeNN(Image *image,int newWidth,int newHeight) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); + + // EDIT: added +1 to account for an early rounding problem + int xRatio = (int)((image->width << 16)/newWidth) + 1; + int yRatio = (int)((image->height << 16)/newHeight) + 1; + + int x2, y2; + for (int y = 0; y < newHeight; y++) + { + for (int x = 0; x < newWidth; x++) + { + x2 = ((x*xRatio) >> 16); + y2 = ((y*yRatio) >> 16); + + output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ; + } + } + + int format = image->format; + + RL_FREE(image->data); + + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); // Reformat 32bit RGBA image to original format + + UnloadImageColors(pixels); +} + +// Resize canvas and fill with color +// NOTE: Resize offset is relative to the top-left corner of the original image +void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else if ((newWidth != image->width) || (newHeight != image->height)) + { + Rectangle srcRec = { 0, 0, (float)image->width, (float)image->height }; + Vector2 dstPos = { (float)offsetX, (float)offsetY }; + + if (offsetX < 0) + { + srcRec.x = (float)-offsetX; + srcRec.width += (float)offsetX; + dstPos.x = 0; + } + else if ((offsetX + image->width) > newWidth) srcRec.width = (float)(newWidth - offsetX); + + if (offsetY < 0) + { + srcRec.y = (float)-offsetY; + srcRec.height += (float)offsetY; + dstPos.y = 0; + } + else if ((offsetY + image->height) > newHeight) srcRec.height = (float)(newHeight - offsetY); + + if (newWidth < srcRec.width) srcRec.width = (float)newWidth; + if (newHeight < srcRec.height) srcRec.height = (float)newHeight; + + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *resizedData = (unsigned char *)RL_CALLOC(newWidth*newHeight*bytesPerPixel, 1); + + // TODO: Fill resizedData with fill color (must be formatted to image->format) + + int dstOffsetSize = ((int)dstPos.y*newWidth + (int)dstPos.x)*bytesPerPixel; + + for (int y = 0; y < (int)srcRec.height; y++) + { + memcpy(resizedData + dstOffsetSize, ((unsigned char *)image->data) + ((y + (int)srcRec.y)*image->width + (int)srcRec.x)*bytesPerPixel, (int)srcRec.width*bytesPerPixel); + dstOffsetSize += (newWidth*bytesPerPixel); + } + + RL_FREE(image->data); + image->data = resizedData; + image->width = newWidth; + image->height = newHeight; + } +} + +// Generate all mipmap levels for a provided image +// NOTE 1: Supports POT and NPOT images +// NOTE 2: image.data is scaled to include mipmap levels +// NOTE 3: Mipmaps format is the same as base image +void ImageMipmaps(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + int mipCount = 1; // Required mipmap levels count (including base level) + int mipWidth = image->width; // Base image width + int mipHeight = image->height; // Base image height + int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); // Image data size (in bytes) + + // Count mipmap levels required + while ((mipWidth != 1) || (mipHeight != 1)) + { + if (mipWidth != 1) mipWidth /= 2; + if (mipHeight != 1) mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + TRACELOGD("IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); + + mipCount++; + mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes) + } + + if (image->mipmaps < mipCount) + { + void *temp = RL_REALLOC(image->data, mipSize); + + if (temp != NULL) image->data = temp; // Assign new pointer (new size) to store mipmaps data + else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps required memory could not be allocated"); + + // Pointer to allocated memory point where store next mipmap level data + unsigned char *nextmip = (unsigned char *)image->data + GetPixelDataSize(image->width, image->height, image->format); + + mipWidth = image->width/2; + mipHeight = image->height/2; + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + Image imCopy = ImageCopy(*image); + + for (int i = 1; i < mipCount; i++) + { + TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); + + ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter + + memcpy(nextmip, imCopy.data, mipSize); + nextmip += mipSize; + image->mipmaps++; + + mipWidth /= 2; + mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + } + + UnloadImage(imCopy); + } + else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps already available"); +} + +// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +// NOTE: In case selected bpp do not represent an known 16bit format, +// dithered data is stored in the LSB part of the unsigned short +void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered"); + return; + } + + if ((rBpp + gBpp + bBpp + aBpp) > 16) + { + TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); + } + else + { + Color *pixels = LoadImageColors(*image); + + RL_FREE(image->data); // free old image data + + if ((image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) + { + TRACELOG(LOG_WARNING, "IMAGE: Format is already 16bpp or lower, dithering could have no effect"); + } + + // Define new image format, check if desired bpp match internal known format + if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + else + { + image->format = 0; + TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); + } + + // NOTE: We will store the dithered data as unsigned short (16bpp) + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + Color oldPixel = WHITE; + Color newPixel = WHITE; + + int rError, gError, bError; + unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition + + #define MIN(a,b) (((a)<(b))?(a):(b)) + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + oldPixel = pixels[y*image->width + x]; + + // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) + newPixel.r = oldPixel.r >> (8 - rBpp); // R bits + newPixel.g = oldPixel.g >> (8 - gBpp); // G bits + newPixel.b = oldPixel.b >> (8 - bBpp); // B bits + newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) + + // NOTE: Error must be computed between new and old pixel but using same number of bits! + // We want to know how much color precision we have lost... + rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); + gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); + bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); + + pixels[y*image->width + x] = newPixel; + + // NOTE: Some cases are out of the array and should be ignored + if (x < (image->width - 1)) + { + pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); + pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); + pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); + } + + if ((x > 0) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff); + } + + if (y < (image->height - 1)) + { + pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); + } + + if ((x < (image->width - 1)) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff); + } + + rPixel = (unsigned short)newPixel.r; + gPixel = (unsigned short)newPixel.g; + bPixel = (unsigned short)newPixel.b; + aPixel = (unsigned short)newPixel.a; + + ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; + } + } + + UnloadImageColors(pixels); + } +} + +// Flip image vertically +void ImageFlipVertical(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int i = (image->height - 1), offsetSize = 0; i >= 0; i--) + { + memcpy(flippedData + offsetSize, ((unsigned char *)image->data) + i*image->width*bytesPerPixel, image->width*bytesPerPixel); + offsetSize += image->width*bytesPerPixel; + } + + RL_FREE(image->data); + image->data = flippedData; + } +} + +// Flip image horizontally +void ImageFlipHorizontal(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + // OPTION 1: Move pixels with memcopy() + //memcpy(flippedData + (y*image->width + x)*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - 1 - x))*bytesPerPixel, bytesPerPixel); + + // OPTION 2: Just copy data pixel by pixel + for (int i = 0; i < bytesPerPixel; i++) flippedData[(y*image->width + x)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - 1 - x))*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = flippedData; + + /* + // OPTION 3: Faster implementation (specific for 32bit pixels) + // NOTE: It does not require additional allocations + uint32_t *ptr = (uint32_t *)image->data; + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width/2; x++) + { + uint32_t backup = ptr[y*image->width + x]; + ptr[y*image->width + x] = ptr[y*image->width + (image->width - 1 - x)]; + ptr[y*image->width + (image->width - 1 - x)] = backup; + } + } + */ + } +} + +// Rotate image clockwise 90deg +void ImageRotateCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + (image->height - y - 1))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + (image->height - y - 1))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; + + image->width = height; + image->height = width; + } +} + +// Rotate image counter-clockwise 90deg +void ImageRotateCCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + y))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - x - 1))*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + y)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - x - 1))*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; + + image->width = height; + image->height = width; + } +} + +// Modify image color: tint +void ImageColorTint(Image *image, Color color) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + float cR = (float)color.r/255; + float cG = (float)color.g/255; + float cB = (float)color.b/255; + float cA = (float)color.a/255; + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + int index = y*image->width + x; + unsigned char r = (unsigned char)(((float)pixels[index].r/255*cR)*255.0f); + unsigned char g = (unsigned char)(((float)pixels[index].g/255*cG)*255.0f); + unsigned char b = (unsigned char)(((float)pixels[index].b/255*cB)*255.0f); + unsigned char a = (unsigned char)(((float)pixels[index].a/255*cA)*255.0f); + + pixels[y*image->width + x].r = r; + pixels[y*image->width + x].g = g; + pixels[y*image->width + x].b = b; + pixels[y*image->width + x].a = a; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: invert +void ImageColorInvert(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + pixels[y*image->width + x].r = 255 - pixels[y*image->width + x].r; + pixels[y*image->width + x].g = 255 - pixels[y*image->width + x].g; + pixels[y*image->width + x].b = 255 - pixels[y*image->width + x].b; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: grayscale +void ImageColorGrayscale(Image *image) +{ + ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); +} + +// Modify image color: contrast +// NOTE: Contrast values between -100 and 100 +void ImageColorContrast(Image *image, float contrast) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (contrast < -100) contrast = -100; + if (contrast > 100) contrast = 100; + + contrast = (100.0f + contrast)/100.0f; + contrast *= contrast; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + float pR = (float)pixels[y*image->width + x].r/255.0f; + pR -= 0.5; + pR *= contrast; + pR += 0.5; + pR *= 255; + if (pR < 0) pR = 0; + if (pR > 255) pR = 255; + + float pG = (float)pixels[y*image->width + x].g/255.0f; + pG -= 0.5; + pG *= contrast; + pG += 0.5; + pG *= 255; + if (pG < 0) pG = 0; + if (pG > 255) pG = 255; + + float pB = (float)pixels[y*image->width + x].b/255.0f; + pB -= 0.5; + pB *= contrast; + pB += 0.5; + pB *= 255; + if (pB < 0) pB = 0; + if (pB > 255) pB = 255; + + pixels[y*image->width + x].r = (unsigned char)pR; + pixels[y*image->width + x].g = (unsigned char)pG; + pixels[y*image->width + x].b = (unsigned char)pB; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: brightness +// NOTE: Brightness values between -255 and 255 +void ImageColorBrightness(Image *image, int brightness) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (brightness < -255) brightness = -255; + if (brightness > 255) brightness = 255; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + int cR = pixels[y*image->width + x].r + brightness; + int cG = pixels[y*image->width + x].g + brightness; + int cB = pixels[y*image->width + x].b + brightness; + + if (cR < 0) cR = 1; + if (cR > 255) cR = 255; + + if (cG < 0) cG = 1; + if (cG > 255) cG = 255; + + if (cB < 0) cB = 1; + if (cB > 255) cB = 255; + + pixels[y*image->width + x].r = (unsigned char)cR; + pixels[y*image->width + x].g = (unsigned char)cG; + pixels[y*image->width + x].b = (unsigned char)cB; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: replace color +void ImageColorReplace(Image *image, Color color, Color replace) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + if ((pixels[y*image->width + x].r == color.r) && + (pixels[y*image->width + x].g == color.g) && + (pixels[y*image->width + x].b == color.b) && + (pixels[y*image->width + x].a == color.a)) + { + pixels[y*image->width + x].r = replace.r; + pixels[y*image->width + x].g = replace.g; + pixels[y*image->width + x].b = replace.b; + pixels[y*image->width + x].a = replace.a; + } + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} +#endif // SUPPORT_IMAGE_MANIPULATION + +// Load color data from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImageColors(); +Color *LoadImageColors(Image image) +{ + if ((image.width == 0) || (image.height == 0)) return NULL; + + Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + if ((image.format == PIXELFORMAT_UNCOMPRESSED_R32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); + + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].r = ((unsigned char *)image.data)[i]; + pixels[i].g = ((unsigned char *)image.data)[i]; + pixels[i].b = ((unsigned char *)image.data)[i]; + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = ((unsigned char *)image.data)[k + 1]; + + k += 2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); + pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); + pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); + pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k + 1]; + pixels[i].b = ((unsigned char *)image.data)[k + 2]; + pixels[i].a = ((unsigned char *)image.data)[k + 3]; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; + pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; + pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].a = (unsigned char)(((float *)image.data)[k]*255.0f); + + k += 4; + } break; + default: break; + } + } + } + + return pixels; +} + +// Load colors palette from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImagePalette() +Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorsCount) +{ + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + int palCount = 0; + Color *palette = NULL; + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) + { + palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); + + for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + + for (int i = 0; i < image.width*image.height; i++) + { + if (pixels[i].a > 0) + { + bool colorInPalette = false; + + // Check if the color is already on palette + for (int j = 0; j < maxPaletteSize; j++) + { + if (COLOR_EQUAL(pixels[i], palette[j])) + { + colorInPalette = true; + break; + } + } + + // Store color if not on the palette + if (!colorInPalette) + { + palette[palCount] = pixels[i]; // Add pixels[i] to palette + palCount++; + + // We reached the limit of colors supported by palette + if (palCount >= maxPaletteSize) + { + i = image.width*image.height; // Finish palette get + TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); + } + } + } + } + + UnloadImageColors(pixels); + } + + *colorsCount = palCount; + + return palette; +} + +// Unload color data loaded with LoadImageColors() +void UnloadImageColors(Color *colors) +{ + RL_FREE(colors); +} + +// Unload colors palette loaded with LoadImagePalette() +void UnloadImagePalette(Color *colors) +{ + RL_FREE(colors); +} + +// Get pixel data from image as Vector4 array (float normalized) +static Vector4 *LoadImageDataNormalized(Image image) +{ + Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; + + k += 2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); + pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); + pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); + pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); + pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); + pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); + pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = 0.0f; + pixels[i].z = 0.0f; + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = ((float *)image.data)[k + 3]; + + k += 4; + } + default: break; + } + } + } + + return pixels; +} + +// Get image alpha border rectangle +// NOTE: Threshold is defined as a percentatge: 0.0f -> 1.0f +Rectangle GetImageAlphaBorder(Image image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) + { + int xMin = 65536; // Define a big enough number + int xMax = 0; + int yMin = 65536; + int yMax = 0; + + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) + { + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + } + + // Check for empty blank image + if ((xMin != 65536) && (xMax != 65536)) + { + crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; + } + + UnloadImageColors(pixels); + } + + return crop; +} + +//------------------------------------------------------------------------------------ +// Image drawing functions +//------------------------------------------------------------------------------------ +// Clear image background with given color +void ImageClearBackground(Image *dst, Color color) +{ + for (int i = 0; i < dst->width*dst->height; ++i) ImageDrawPixel(dst, i%dst->width, i/dst->width, color); +} + +// Draw pixel within an image +// NOTE: Compressed image formats not supported +void ImageDrawPixel(Image *dst, int x, int y, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; + + switch (dst->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[y*dst->width + x] = gray; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; + ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // NOTE: Calculate grayscale equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; + ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; + + } break; + default: break; + } +} + +// Draw pixel within an image (Vector version) +void ImageDrawPixelV(Image *dst, Vector2 position, Color color) +{ + ImageDrawPixel(dst, (int)position.x, (int)position.y, color); +} + +// Draw line within an image +void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + int m = 2*(endPosY - startPosY); + int slopeError = m - (endPosX - startPosX); + + for (int x = startPosX, y = startPosY; x <= endPosX; x++) + { + ImageDrawPixel(dst, x, y, color); + slopeError += m; + + if (slopeError >= 0) + { + y++; + slopeError -= 2*(endPosX - startPosX); + } + } +} + +// Draw line within an image (Vector version) +void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) +{ + ImageDrawLine(dst, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color); +} + +// Draw circle within an image +void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) +{ + int x = 0, y = radius; + int decesionParameter = 3 - 2*radius; + + while (y >= x) + { + ImageDrawPixel(dst, centerX + x, centerY + y, color); + ImageDrawPixel(dst, centerX - x, centerY + y, color); + ImageDrawPixel(dst, centerX + x, centerY - y, color); + ImageDrawPixel(dst, centerX - x, centerY - y, color); + ImageDrawPixel(dst, centerX + y, centerY + x, color); + ImageDrawPixel(dst, centerX - y, centerY + x, color); + ImageDrawPixel(dst, centerX + y, centerY - x, color); + ImageDrawPixel(dst, centerX - y, centerY - x, color); + x++; + + if (decesionParameter > 0) + { + y--; + decesionParameter = decesionParameter + 4*(x - y) + 10; + } + else decesionParameter = decesionParameter + 4*x + 6; + } +} + +// Draw circle within an image (Vector version) +void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) +{ + ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); +} + +// Draw rectangle within an image +void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) +{ + ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); +} + +// Draw rectangle within an image (Vector version) +void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) +{ + ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); +} + +// Draw rectangle within an image +void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; + + int sy = (int)rec.y; + int ey = sy + (int)rec.height; + + int sx = (int)rec.x; + int ex = sx + (int)rec.width; + + for (int y = sy; y < ey; y++) + { + for (int x = sx; x < ex; x++) + { + ImageDrawPixel(dst, x, y, color); + } + } +} + +// Draw rectangle lines within an image +void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) +{ + ImageDrawRectangle(dst, (int)rec.x, (int)rec.y, (int)rec.width, thick, color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)(rec.x + rec.width - thick), (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + rec.height - thick), (int)rec.width, thick, color); +} + +// Draw an image (source) within an image (destination) +// NOTE: Color tint is applied to source image +void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || + (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; + + if (dst->mipmaps > 1) TRACELOG(LOG_WARNING, "Image drawing only applied to base mipmap level"); + if (dst->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); + else + { + Image srcMod = { 0 }; // Source copy (in case it was required) + Image *srcPtr = &src; // Pointer to source image + bool useSrcMod = false; // Track source copy required + + // Source rectangle out-of-bounds security checks + if (srcRec.x < 0) { srcRec.width += srcRec.x; srcRec.x = 0; } + if (srcRec.y < 0) { srcRec.height += srcRec.y; srcRec.y = 0; } + if ((srcRec.x + srcRec.width) > src.width) srcRec.width = src.width - srcRec.x; + if ((srcRec.y + srcRec.height) > src.height) srcRec.height = src.height - srcRec.y; + + // Check if source rectangle needs to be resized to destination rectangle + // In that case, we make a copy of source and we apply all required transform + if (((int)srcRec.width != (int)dstRec.width) || ((int)srcRec.height != (int)dstRec.height)) + { + srcMod = ImageFromImage(src, srcRec); // Create image from another image + ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); // Resize to destination rectangle + srcRec = (Rectangle){ 0, 0, (float)srcMod.width, (float)srcMod.height }; + + srcPtr = &srcMod; + useSrcMod = true; + } + + // Destination rectangle out-of-bounds security checks + if (dstRec.x < 0) + { + srcRec.x = -dstRec.x; + srcRec.width += dstRec.x; + dstRec.x = 0; + } + else if ((dstRec.x + srcRec.width) > dst->width) srcRec.width = dst->width - dstRec.x; + + if (dstRec.y < 0) + { + srcRec.y = -dstRec.y; + srcRec.height += dstRec.y; + dstRec.y = 0; + } + else if ((dstRec.y + srcRec.height) > dst->height) srcRec.height = dst->height - dstRec.y; + + if (dst->width < srcRec.width) srcRec.width = (float)dst->width; + if (dst->height < srcRec.height) srcRec.height = (float)dst->height; + + // This blitting method is quite fast! The process followed is: + // for every pixel -> [get_src_format/get_dst_format -> blend -> format_to_dst] + // Some optimization ideas: + // [x] Avoid creating source copy if not required (no resize required) + // [x] Optimize ImageResize() for pixel format (alternative: ImageResizeNN()) + // [x] Optimize ColorAlphaBlend() to avoid processing (alpha = 0) and (alpha = 1) + // [x] Optimize ColorAlphaBlend() for faster operations (maybe avoiding divs?) + // [x] Consider fast path: no alpha blending required cases (src has no alpha) + // [x] Consider fast path: same src/dst format with no alpha -> direct line copy + // [-] GetPixelColor(): Return Vector4 instead of Color, easier for ColorAlphaBlend() + + Color colSrc, colDst, blend; + bool blendRequired = true; + + // Fast path: Avoid blend if source has no alpha to blend + if ((tint.a == 255) && ((srcPtr->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R5G6B5))) blendRequired = false; + + int strideDst = GetPixelDataSize(dst->width, 1, dst->format); + int bytesPerPixelDst = strideDst/(dst->width); + + int strideSrc = GetPixelDataSize(srcPtr->width, 1, srcPtr->format); + int bytesPerPixelSrc = strideSrc/(srcPtr->width); + + unsigned char *pSrcBase = (unsigned char *)srcPtr->data + ((int)srcRec.y*srcPtr->width + (int)srcRec.x)*bytesPerPixelSrc; + unsigned char *pDstBase = (unsigned char *)dst->data + ((int)dstRec.y*dst->width + (int)dstRec.x)*bytesPerPixelDst; + + for (int y = 0; y < (int)srcRec.height; y++) + { + unsigned char *pSrc = pSrcBase; + unsigned char *pDst = pDstBase; + + // Fast path: Avoid moving pixel by pixel if no blend required and same format + if (!blendRequired && (srcPtr->format == dst->format)) memcpy(pDst, pSrc, (int)(srcRec.width)*bytesPerPixelSrc); + else + { + for (int x = 0; x < (int)srcRec.width; x++) + { + colSrc = GetPixelColor(pSrc, srcPtr->format); + colDst = GetPixelColor(pDst, dst->format); + + // Fast path: Avoid blend if source has no alpha to blend + if (blendRequired) blend = ColorAlphaBlend(colDst, colSrc, tint); + else blend = colSrc; + + SetPixelColor(pDst, blend, dst->format); + + pDst += bytesPerPixelDst; + pSrc += bytesPerPixelSrc; + } + } + + pSrcBase += strideSrc; + pDstBase += strideDst; + } + + if (useSrcMod) UnloadImage(srcMod); // Unload source modified image + } +} + +// Draw text (default font) within an image (destination) +void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) +{ + Vector2 position = { (float)posX, (float)posY }; + + // NOTE: For default font, sapcing is set to desired font size / default font size (10) + ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color); +} + +// Draw text (custom sprite font) within an image (destination) +void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) +{ + Image imText = ImageTextEx(font, text, fontSize, spacing, tint); + + Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; + Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; + + ImageDraw(dst, imText, srcRec, dstRec, WHITE); + + UnloadImage(imText); +} + +//------------------------------------------------------------------------------------ +// Texture loading functions +//------------------------------------------------------------------------------------ +// Load texture from file into GPU memory (VRAM) +Texture2D LoadTexture(const char *fileName) +{ + Texture2D texture = { 0 }; + + Image image = LoadImage(fileName); + + if (image.data != NULL) + { + texture = LoadTextureFromImage(image); + UnloadImage(image); + } + + return texture; +} + +// Load a texture from image data +// NOTE: image is not unloaded, it must be done manually +Texture2D LoadTextureFromImage(Image image) +{ + Texture2D texture = { 0 }; + + if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) + { + texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); + } + else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); + + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; + + return texture; +} + +// Load cubemap from image, multiple image cubemap layouts supported +TextureCubemap LoadTextureCubemap(Image image, int layout) +{ + TextureCubemap cubemap = { 0 }; + + if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type + { + // Check image width/height to determine the type of cubemap provided + if (image.width > image.height) + { + if ((image.width/6) == image.height) { layout = CUBEMAP_LAYOUT_LINE_HORIZONTAL; cubemap.width = image.width/6; } + else if ((image.width/4) == (image.height/3)) { layout = CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } + else if (image.width >= (int)((float)image.height*1.85f)) { layout = CUBEMAP_LAYOUT_PANORAMA; cubemap.width = image.width/4; } + } + else if (image.height > image.width) + { + if ((image.height/6) == image.width) { layout = CUBEMAP_LAYOUT_LINE_VERTICAL; cubemap.width = image.height/6; } + else if ((image.width/3) == (image.height/4)) { layout = CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } + } + + cubemap.height = cubemap.width; + } + + if (layout != CUBEMAP_LAYOUT_AUTO_DETECT) + { + int size = cubemap.width; + + Image faces = { 0 }; // Vertical column image + Rectangle faceRecs[6] = { 0 }; // Face source rectangles + for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; + + if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) + { + faces = image; + for (int i = 0; i < 6; i++) faceRecs[i].y = (float)size*i; + } + else if (layout == CUBEMAP_LAYOUT_PANORAMA) + { + // TODO: Convert panorama image to square faces... + // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp + } + else + { + if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; + else if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) + { + faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; + faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = 0; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; + } + else if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) + { + faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; + faceRecs[1].x = 0; faceRecs[1].y = (float)size; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = (float)size; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*3; faceRecs[5].y = (float)size; + } + + // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + faces = GenImageColor(size, size*6, MAGENTA); + ImageFormat(&faces, image.format); + + // TODO: Image formating does not work with compressed textures! + } + + for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); + + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); + if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); + + UnloadImage(faces); + } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); + + return cubemap; +} + +// Load texture for rendering (framebuffer) +// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer +RenderTexture2D LoadRenderTexture(int width, int height) +{ + RenderTexture2D target = { 0 }; + + target.id = rlLoadFramebuffer(width, height); // Load an empty framebuffer + + if (target.id > 0) + { + rlEnableFramebuffer(target.id); + + // Create color texture (default to RGBA) + target.texture.id = rlLoadTexture(NULL, width, height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + target.texture.width = width; + target.texture.height = height; + target.texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + target.texture.mipmaps = 1; + + // Create depth renderbuffer/texture + target.depth.id = rlLoadTextureDepth(width, height, true); + target.depth.width = width; + target.depth.height = height; + target.depth.format = 19; //DEPTH_COMPONENT_24BIT? + target.depth.mipmaps = 1; + + // Attach color texture and depth renderbuffer/texture to FBO + rlFramebufferAttach(target.id, target.texture.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(target.id, target.depth.id, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); + + // Check if fbo is complete with attachments (valid) + if (rlFramebufferComplete(target.id)) TRACELOG(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", target.id); + + rlDisableFramebuffer(); + } + else TRACELOG(LOG_WARNING, "FBO: Framebuffer object can not be created"); + + return target; +} + +// Unload texture from GPU memory (VRAM) +void UnloadTexture(Texture2D texture) +{ + if (texture.id > 0) + { + rlUnloadTexture(texture.id); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + } +} + +// Unload render texture from GPU memory (VRAM) +void UnloadRenderTexture(RenderTexture2D target) +{ + if (target.id > 0) + { + // Color texture attached to FBO is deleted + rlUnloadTexture(target.texture.id); + + // NOTE: Depth texture/renderbuffer is automatically + // queried and deleted before deleting framebuffer + rlUnloadFramebuffer(target.id); + } +} + +// Update GPU texture with new data +// NOTE: pixels data must match texture.format +void UpdateTexture(Texture2D texture, const void *pixels) +{ + rlUpdateTexture(texture.id, 0, 0, texture.width, texture.height, texture.format, pixels); +} + +// Update GPU texture rectangle with new data +// NOTE: pixels data must match texture.format +void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels) +{ + rlUpdateTexture(texture.id, (int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, texture.format, pixels); +} + +// Get pixel data from GPU texture and return an Image +// NOTE: Compressed texture formats not supported +Image GetTextureData(Texture2D texture) +{ + Image image = { 0 }; + + if (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + image.data = rlReadTexturePixels(texture); + + if (image.data != NULL) + { + image.width = texture.width; + image.height = texture.height; + image.format = texture.format; + image.mipmaps = 1; + +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, + // coming from FBO color buffer attachment, but it seems + // original texture format is retrieved on RPI... + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; +#endif + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); + + return image; +} + +// Get pixel data from GPU frontbuffer and return an Image (screenshot) +Image GetScreenData(void) +{ + Image image = { 0 }; + + image.width = GetScreenWidth(); + image.height = GetScreenHeight(); + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + image.data = rlReadScreenPixels(image.width, image.height); + + return image; +} + +//------------------------------------------------------------------------------------ +// Texture configuration functions +//------------------------------------------------------------------------------------ +// Generate GPU mipmaps for a texture +void GenTextureMipmaps(Texture2D *texture) +{ + // NOTE: NPOT textures support check inside function + // On WebGL (OpenGL ES 2.0) NPOT textures support is limited + rlGenerateMipmaps(texture); +} + +// Set texture scaling filter mode +void SetTextureFilter(Texture2D texture, int filter) +{ + switch (filter) + { + case TEXTURE_FILTER_POINT: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_NEAREST); + + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); + } + else + { + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_NEAREST); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); + } + } break; + case TEXTURE_FILTER_BILINEAR: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) + // Alternative: RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + else + { + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + } break; + case TEXTURE_FILTER_TRILINEAR: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_LINEAR); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + else + { + TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] No mipmaps available for TRILINEAR texture filtering", texture.id); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + } break; + case TEXTURE_FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 4); break; + case TEXTURE_FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 8); break; + case TEXTURE_FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 16); break; + default: break; + } +} + +// Set texture wrapping mode +void SetTextureWrap(Texture2D texture, int wrap) +{ + switch (wrap) + { + case TEXTURE_WRAP_REPEAT: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT); + } break; + case TEXTURE_WRAP_CLAMP: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_CLAMP); + } break; + case TEXTURE_WRAP_MIRROR_REPEAT: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_REPEAT); + } break; + case TEXTURE_WRAP_MIRROR_CLAMP: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_CLAMP); + } break; + default: break; + } +} + +//------------------------------------------------------------------------------------ +// Texture drawing functions +//------------------------------------------------------------------------------------ +// Draw a Texture2D +void DrawTexture(Texture2D texture, int posX, int posY, Color tint) +{ + DrawTextureEx(texture, (Vector2){ (float)posX, (float)posY }, 0.0f, 1.0f, tint); +} + +// Draw a Texture2D with position defined as Vector2 +void DrawTextureV(Texture2D texture, Vector2 position, Color tint) +{ + DrawTextureEx(texture, position, 0, 1.0f, tint); +} + +// Draw a Texture2D with extended parameters +void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint) +{ + Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + Rectangle dest = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, source, dest, origin, rotation, tint); +} + +// Draw a part of a texture (defined by a rectangle) +void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint) +{ + Rectangle dest = { position.x, position.y, fabsf(source.width), fabsf(source.height) }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, source, dest, origin, 0.0f, tint); +} + +// Draw texture quad with tiling and offset parameters +// NOTE: Tiling and offset should be provided considering normalized texture values [0..1] +// i.e tiling = { 1.0f, 1.0f } refers to all texture, offset = { 0.5f, 0.5f } moves texture origin to center +void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint) +{ + Rectangle source = { offset.x*texture.width, offset.y*texture.height, tiling.x*texture.width, tiling.y*texture.height }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, source, quad, origin, 0.0f, tint); +} + +// Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +// NOTE: For tilling a whole texture DrawTextureQuad() is better +void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint) +{ + if ((texture.id <= 0) || (scale <= 0.0f)) return; // Wanna see a infinite loop?!...just delete this line! + + int tileWidth = (int)(source.width*scale), tileHeight = (int)(source.height*scale); + if ((dest.width < tileWidth) && (dest.height < tileHeight)) + { + // Can fit only one tile + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y, dest.width, dest.height}, origin, rotation, tint); + } + else if (dest.width <= tileWidth) + { + // Tiled vertically (one column) + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, source.height}, (Rectangle){dest.x, dest.y + dy, dest.width, (float)tileHeight}, origin, rotation, tint); + } + + // Fit last tile + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y + dy, dest.width, dest.height - dy}, origin, rotation, tint); + } + } + else if (dest.height <= tileHeight) + { + // Tiled horizontally (one row) + int dx = 0; + for (;dx+tileWidth < dest.width; dx += tileWidth) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)dest.height/tileHeight)*source.height}, (Rectangle){dest.x + dx, dest.y, (float)tileWidth, dest.height}, origin, rotation, tint); + } + + // Fit last tile + if (dx < dest.width) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y, dest.width - dx, dest.height}, origin, rotation, tint); + } + } + else + { + // Tiled both horizontally and vertically (rows and columns) + int dx = 0; + for (;dx+tileWidth < dest.width; dx += tileWidth) + { + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, source, (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, (float)tileHeight}, origin, rotation, tint); + } + + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, dest.height - dy}, origin, rotation, tint); + } + } + + // Fit last column of tiles + if (dx < dest.width) + { + int dy = 0; + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, (float)tileHeight}, origin, rotation, tint); + } + + // Draw final tile in the bottom right corner + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, dest.height - dy}, origin, rotation, tint); + } + } + } +} + +// Draw a part of a texture (defined by a rectangle) with 'pro' parameters +// NOTE: origin is relative to destination rectangle size +void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint) +{ + // Check if texture is valid + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + bool flipX = false; + + if (source.width < 0) { flipX = true; source.width *= -1; } + if (source.height < 0) source.y -= source.height; + + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = dest.x - origin.x; + float y = dest.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + dest.width, y }; + bottomLeft = (Vector2){ x, y + dest.height }; + bottomRight = (Vector2){ x + dest.width, y + dest.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = dest.x; + float y = dest.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + dest.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + dest.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + dest.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + dest.height)*cosRotation; + + bottomRight.x = x + (dx + dest.width)*cosRotation - (dy + dest.height)*sinRotation; + bottomRight.y = y + (dx + dest.width)*sinRotation + (dy + dest.height)*cosRotation; + } + + rlCheckRenderBatchLimit(4); // Make sure there is enough free space on the batch buffer + + rlSetTexture(texture.id); + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); + rlVertex2f(topLeft.x, topLeft.y); + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(bottomRight.x, bottomRight.y); + + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + rlSetTexture(0); + + // NOTE: Vertex position can be transformed using matrices + // but the process is way more costly than just calculating + // the vertex positions manually, like done above. + // I leave here the old implementation for educational pourposes, + // just in case someone wants to do some performance test + /* + rlSetTexture(texture.id); + rlPushMatrix(); + rlTranslatef(dest.x, dest.y, 0.0f); + if (rotation != 0.0f) rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); + rlVertex2f(0.0f, 0.0f); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(0.0f, dest.height); + + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(dest.width, dest.height); + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(dest.width, 0.0f); + rlEnd(); + rlPopMatrix(); + rlSetTexture(0); + */ + } +} + +// Draws a texture (or part of it) that stretches or shrinks nicely using n-patch info +void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint) +{ + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + float patchWidth = (dest.width <= 0.0f)? 0.0f : dest.width; + float patchHeight = (dest.height <= 0.0f)? 0.0f : dest.height; + + if (nPatchInfo.source.width < 0) nPatchInfo.source.x -= nPatchInfo.source.width; + if (nPatchInfo.source.height < 0) nPatchInfo.source.y -= nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) patchHeight = nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) patchWidth = nPatchInfo.source.width; + + bool drawCenter = true; + bool drawMiddle = true; + float leftBorder = (float)nPatchInfo.left; + float topBorder = (float)nPatchInfo.top; + float rightBorder = (float)nPatchInfo.right; + float bottomBorder = (float)nPatchInfo.bottom; + + // adjust the lateral (left and right) border widths in case patchWidth < texture.width + if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_VERTICAL) + { + drawCenter = false; + leftBorder = (leftBorder/(leftBorder + rightBorder))*patchWidth; + rightBorder = patchWidth - leftBorder; + } + // adjust the lateral (top and bottom) border heights in case patchHeight < texture.height + if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_HORIZONTAL) + { + drawMiddle = false; + topBorder = (topBorder/(topBorder + bottomBorder))*patchHeight; + bottomBorder = patchHeight - topBorder; + } + + Vector2 vertA, vertB, vertC, vertD; + vertA.x = 0.0f; // outer left + vertA.y = 0.0f; // outer top + vertB.x = leftBorder; // inner left + vertB.y = topBorder; // inner top + vertC.x = patchWidth - rightBorder; // inner right + vertC.y = patchHeight - bottomBorder; // inner bottom + vertD.x = patchWidth; // outer right + vertD.y = patchHeight; // outer bottom + + Vector2 coordA, coordB, coordC, coordD; + coordA.x = nPatchInfo.source.x/width; + coordA.y = nPatchInfo.source.y/height; + coordB.x = (nPatchInfo.source.x + leftBorder)/width; + coordB.y = (nPatchInfo.source.y + topBorder)/height; + coordC.x = (nPatchInfo.source.x + nPatchInfo.source.width - rightBorder)/width; + coordC.y = (nPatchInfo.source.y + nPatchInfo.source.height - bottomBorder)/height; + coordD.x = (nPatchInfo.source.x + nPatchInfo.source.width)/width; + coordD.y = (nPatchInfo.source.y + nPatchInfo.source.height)/height; + + rlSetTexture(texture.id); + + rlPushMatrix(); + rlTranslatef(dest.x, dest.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + if (nPatchInfo.layout == NPATCH_NINE_PATCH) + { + // ------------------------------------------------------------ + // TOP-LEFT QUAD + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // TOP-CENTER QUAD + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad + } + // TOP-RIGHT QUAD + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad + if (drawMiddle) + { + // ------------------------------------------------------------ + // MIDDLE-LEFT QUAD + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad + if (drawCenter) + { + // MIDDLE-CENTER QUAD + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-left corner for texture and quad + } + + // MIDDLE-RIGHT QUAD + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-left corner for texture and quad + } + + // ------------------------------------------------------------ + // BOTTOM-LEFT QUAD + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad + if (drawCenter) + { + // BOTTOM-CENTER QUAD + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-left corner for texture and quad + } + + // BOTTOM-RIGHT QUAD + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-left corner for texture and quad + } + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) + { + // TOP QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // MIDDLE QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad + } + // BOTTOM QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad + } + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) + { + // LEFT QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // CENTER QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad + } + // RIGHT QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad + } + rlEnd(); + rlPopMatrix(); + + rlSetTexture(0); + } +} + +// Draw textured polygon, defined by vertex and texturecoordinates +// NOTE: Polygon center must have straight line path to all points +// without crossing perimeter, points must be in anticlockwise order +void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointsCount, Color tint) +{ + rlCheckRenderBatchLimit((pointsCount - 1)*4); + + rlSetTexture(texture.id); + + // Texturing is only supported on QUADs + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + + for (int i = 0; i < pointsCount - 1; i++) + { + rlTexCoord2f(0.5f, 0.5f); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texcoords[i].x, texcoords[i].y); + rlVertex2f(points[i].x + center.x, points[i].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + } + rlEnd(); + + rlSetTexture(0); +} + +// Returns color with alpha applied, alpha goes from 0.0f to 1.0f +Color Fade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; +} + +// Returns hexadecimal value for a Color +int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Returns color normalized as float [0..1] +Vector4 ColorNormalize(Color color) +{ + Vector4 result; + + result.x = (float)color.r/255.0f; + result.y = (float)color.g/255.0f; + result.z = (float)color.b/255.0f; + result.w = (float)color.a/255.0f; + + return result; +} + +// Returns color from normalized values [0..1] +Color ColorFromNormalized(Vector4 normalized) +{ + Color result; + + result.r = (unsigned char)(normalized.x*255.0f); + result.g = (unsigned char)(normalized.y*255.0f); + result.b = (unsigned char)(normalized.z*255.0f); + result.a = (unsigned char)(normalized.w*255.0f); + + return result; +} + +// Returns HSV values for a Color +// NOTE: Hue is returned as degrees [0..360] +Vector3 ColorToHSV(Color color) +{ + Vector3 hsv = { 0 }; + Vector3 rgb = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + float min, max, delta; + + min = rgb.x < rgb.y? rgb.x : rgb.y; + min = min < rgb.z? min : rgb.z; + + max = rgb.x > rgb.y? rgb.x : rgb.y; + max = max > rgb.z? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = NAN; // Undefined + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Returns a Color from HSV values +// Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion +// NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors +// Hue is provided in degrees: [0..360] +// Saturation/Value are provided normalized: [0.0f..1.0f] +Color ColorFromHSV(float hue, float saturation, float value) +{ + Color color = { 0, 0, 0, 255 }; + + // Red channel + float k = fmodf((5.0f + hue/60.0f), 6); + float t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.r = (unsigned char)((value - value*saturation*k)*255.0f); + + // Green channel + k = fmodf((3.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.g = (unsigned char)((value - value*saturation*k)*255.0f); + + // Blue channel + k = fmodf((1.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.b = (unsigned char)((value - value*saturation*k)*255.0f); + + return color; +} + +// Returns color with alpha applied, alpha goes from 0.0f to 1.0f +Color ColorAlpha(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; +} + +// Returns src alpha-blended into dst color with tint +Color ColorAlphaBlend(Color dst, Color src, Color tint) +{ + Color out = WHITE; + + // Apply color tint to source color + src.r = (unsigned char)(((unsigned int)src.r*(unsigned int)tint.r) >> 8); + src.g = (unsigned char)(((unsigned int)src.g*(unsigned int)tint.g) >> 8); + src.b = (unsigned char)(((unsigned int)src.b*(unsigned int)tint.b) >> 8); + src.a = (unsigned char)(((unsigned int)src.a*(unsigned int)tint.a) >> 8); + +//#define COLORALPHABLEND_FLOAT +#define COLORALPHABLEND_INTEGERS +#if defined(COLORALPHABLEND_INTEGERS) + if (src.a == 0) out = dst; + else if (src.a == 255) out = src; + else + { + unsigned int alpha = (unsigned int)src.a + 1; // We are shifting by 8 (dividing by 256), so we need to take that excess into account + out.a = (unsigned char)(((unsigned int)alpha*256 + (unsigned int)dst.a*(256 - alpha)) >> 8); + + if (out.a > 0) + { + out.r = (unsigned char)((((unsigned int)src.r*alpha*256 + (unsigned int)dst.r*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + out.g = (unsigned char)((((unsigned int)src.g*alpha*256 + (unsigned int)dst.g*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + out.b = (unsigned char)((((unsigned int)src.b*alpha*256 + (unsigned int)dst.b*(unsigned int)dst.a*(256 - alpha))/out.a) >> 8); + } + } +#endif +#if defined(COLORALPHABLEND_FLOAT) + if (src.a == 0) out = dst; + else if (src.a == 255) out = src; + else + { + Vector4 fdst = ColorNormalize(dst); + Vector4 fsrc = ColorNormalize(src); + Vector4 ftint = ColorNormalize(tint); + Vector4 fout = { 0 }; + + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); + + if (fout.w > 0.0f) + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } + + out = (Color){ (unsigned char)(fout.x*255.0f), (unsigned char)(fout.y*255.0f), (unsigned char)(fout.z*255.0f), (unsigned char)(fout.w*255.0f) }; + } +#endif + + return out; +} + +// Returns a Color struct from hexadecimal value +Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Get color from a pixel from certain format +Color GetPixelColor(void *srcPtr, int format) +{ + Color col = { 0 }; + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 5) & 0b0000000000111111)*255/63); + col.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); + col.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 6) & 0b0000000000011111)*255/31); + col.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); + col.a = (((unsigned short *)srcPtr)[0] & 0b0000000000000001)? 255 : 0; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 12)*255/15); + col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 8) & 0b0000000000001111)*255/15); + col.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 4) & 0b0000000000001111)*255/15); + col.a = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000001111)*255/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: break; + default: break; + } + + return col; +} + +// Set pixel color formatted into destination pointer +void SetPixelColor(void *dstPtr, Color color, int format) +{ + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + ((unsigned char *)dstPtr)[1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + ((unsigned char *)dstPtr)[3] = color.a; + + } break; + default: break; + } +} + +// Get pixel data size in bytes for certain format +// NOTE: Size can be requested for Image or Texture data +int GetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_DDS) +// Loading DDS image data (compressed or uncompressed) +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extension: + // GL_EXT_texture_compression_s3tc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 + // GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + // GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + + #define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII + #define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII + #define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII + + // DDS Pixel Format + typedef struct { + unsigned int size; + unsigned int flags; + unsigned int fourCC; + unsigned int rgbBitCount; + unsigned int rBitMask; + unsigned int gBitMask; + unsigned int bBitMask; + unsigned int aBitMask; + } DDSPixelFormat; + + // DDS Header (124 bytes) + typedef struct { + unsigned int size; + unsigned int flags; + unsigned int height; + unsigned int width; + unsigned int pitchOrLinearSize; + unsigned int depth; + unsigned int mipmapCount; + unsigned int reserved1[11]; + DDSPixelFormat ddspf; + unsigned int caps; + unsigned int caps2; + unsigned int caps3; + unsigned int caps4; + unsigned int reserved2; + } DDSHeader; + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + // Verify the type of file + unsigned char *ddsHeaderId = fileDataPtr; + fileDataPtr += 4; + + if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' ')) + { + TRACELOG(LOG_WARNING, "IMAGE: DDS file data not valid"); + } + else + { + DDSHeader *ddsHeader = (DDSHeader *)fileDataPtr; + + TRACELOGD("IMAGE: DDS file data info:"); + TRACELOGD(" > Header size: %i", sizeof(DDSHeader)); + TRACELOGD(" > Pixel format size: %i", ddsHeader->ddspf.size); + TRACELOGD(" > Pixel format flags: 0x%x", ddsHeader->ddspf.flags); + TRACELOGD(" > File format: 0x%x", ddsHeader->ddspf.fourCC); + TRACELOGD(" > File bit count: 0x%x", ddsHeader->ddspf.rgbBitCount); + + fileDataPtr += sizeof(DDSHeader); // Skip header + + image.width = ddsHeader->width; + image.height = ddsHeader->height; + + if (ddsHeader->mipmapCount == 0) image.mipmaps = 1; // Parameter not used + else image.mipmaps = ddsHeader->mipmapCount; + + if (ddsHeader->ddspf.rgbBitCount == 16) // 16bit mode, no compressed + { + if (ddsHeader->ddspf.flags == 0x40) // no alpha channel + { + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + } + else if (ddsHeader->ddspf.flags == 0x41) // with alpha channel + { + if (ddsHeader->ddspf.aBitMask == 0x8000) // 1bit alpha + { + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + unsigned char alpha = 0; + + // NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 15; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 1; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + } + else if (ddsHeader->ddspf.aBitMask == 0xf000) // 4bit alpha + { + int dataSize = image.width*image.height*sizeof(unsigned short); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + unsigned char alpha = 0; + + // NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 12; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 4; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + } + } + } + else if (ddsHeader->ddspf.flags == 0x40 && ddsHeader->ddspf.rgbBitCount == 24) // DDS_RGB, no compressed + { + int dataSize = image.width*image.height*3*sizeof(unsigned char); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + } + else if (ddsHeader->ddspf.flags == 0x41 && ddsHeader->ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed + { + int dataSize = image.width*image.height*4*sizeof(unsigned char); + image.data = (unsigned short *)RL_MALLOC(dataSize); + + memcpy(image.data, fileDataPtr, dataSize); + + unsigned char blue = 0; + + // NOTE: Data comes as A8R8G8B8, it must be reordered R8G8B8A8 (view next comment) + // DirecX understand ARGB as a 32bit DWORD but the actual memory byte alignment is BGRA + // So, we must realign B8G8R8A8 to R8G8B8A8 + for (int i = 0; i < image.width*image.height*4; i += 4) + { + blue = ((unsigned char *)image.data)[i]; + ((unsigned char *)image.data)[i] = ((unsigned char *)image.data)[i + 2]; + ((unsigned char *)image.data)[i + 2] = blue; + } + + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } + else if (((ddsHeader->ddspf.flags == 0x04) || (ddsHeader->ddspf.flags == 0x05)) && (ddsHeader->ddspf.fourCC > 0)) // Compressed + { + int dataSize = 0; + + // Calculate data size, including all mipmaps + if (ddsHeader->mipmapCount > 1) dataSize = ddsHeader->pitchOrLinearSize*2; + else dataSize = ddsHeader->pitchOrLinearSize; + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + switch (ddsHeader->ddspf.fourCC) + { + case FOURCC_DXT1: + { + if (ddsHeader->ddspf.flags == 0x04) image.format = PIXELFORMAT_COMPRESSED_DXT1_RGB; + else image.format = PIXELFORMAT_COMPRESSED_DXT1_RGBA; + } break; + case FOURCC_DXT3: image.format = PIXELFORMAT_COMPRESSED_DXT3_RGBA; break; + case FOURCC_DXT5: image.format = PIXELFORMAT_COMPRESSED_DXT5_RGBA; break; + default: break; + } + } + } + } + + return image; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_PKM) +// Loading PKM image data (ETC1/ETC2 compression) +// NOTE: KTX is the standard Khronos Group compression format (ETC1/ETC2, mipmaps) +// PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps) +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) + // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + // PKM file (ETC1) Header (16 bytes) + typedef struct { + char id[4]; // "PKM " + char version[2]; // "10" or "20" + unsigned short format; // Data format (big-endian) (Check list below) + unsigned short width; // Texture width (big-endian) (origWidth rounded to multiple of 4) + unsigned short height; // Texture height (big-endian) (origHeight rounded to multiple of 4) + unsigned short origWidth; // Original width (big-endian) + unsigned short origHeight; // Original height (big-endian) + } PKMHeader; + + // Formats list + // version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used) + // version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R + + // NOTE: The extended width and height are the widths rounded up to a multiple of 4. + // NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels) + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + PKMHeader *pkmHeader = (PKMHeader *)fileDataPtr; + + if ((pkmHeader->id[0] != 'P') || (pkmHeader->id[1] != 'K') || (pkmHeader->id[2] != 'M') || (pkmHeader->id[3] != ' ')) + { + TRACELOG(LOG_WARNING, "IMAGE: PKM file data not valid"); + } + else + { + fileDataPtr += sizeof(PKMHeader); // Skip header + + // NOTE: format, width and height come as big-endian, data must be swapped to little-endian + pkmHeader->format = ((pkmHeader->format & 0x00FF) << 8) | ((pkmHeader->format & 0xFF00) >> 8); + pkmHeader->width = ((pkmHeader->width & 0x00FF) << 8) | ((pkmHeader->width & 0xFF00) >> 8); + pkmHeader->height = ((pkmHeader->height & 0x00FF) << 8) | ((pkmHeader->height & 0xFF00) >> 8); + + TRACELOGD("IMAGE: PKM file data info:"); + TRACELOGD(" > Image width: %i", pkmHeader->width); + TRACELOGD(" > Image height: %i", pkmHeader->height); + TRACELOGD(" > Image format: %i", pkmHeader->format); + + image.width = pkmHeader->width; + image.height = pkmHeader->height; + image.mipmaps = 1; + + int bpp = 4; + if (pkmHeader->format == 3) bpp = 8; + + int dataSize = image.width*image.height*bpp/8; // Total data size in bytes + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + if (pkmHeader->format == 0) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (pkmHeader->format == 1) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (pkmHeader->format == 3) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; + } + } + + return image; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_KTX) +// Load KTX compressed image data (ETC1/ETC2 compression) +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) + // GL_ARB_ES3_compatibility (ETC2/EAC) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + // KTX file Header (64 bytes) + // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + // v2.0 - http://github.khronos.org/KTX-Specification/ + + // TODO: Support KTX 2.2 specs! + + typedef struct { + char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" + unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 + unsigned int glType; // For compressed textures, glType must equal 0 + unsigned int glTypeSize; // For compressed texture data, usually 1 + unsigned int glFormat; // For compressed textures is 0 + unsigned int glInternalFormat; // Compressed internal format + unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...) + unsigned int width; // Texture image width in pixels + unsigned int height; // Texture image height in pixels + unsigned int depth; // For 2D textures is 0 + unsigned int elements; // Number of array elements, usually 0 + unsigned int faces; // Cubemap faces, for no-cubemap = 1 + unsigned int mipmapLevels; // Non-mipmapped textures = 1 + unsigned int keyValueDataSize; // Used to encode any arbitrary data... + } KTXHeader; + + // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + KTXHeader *ktxHeader = (KTXHeader *)fileDataPtr; + + if ((ktxHeader->id[1] != 'K') || (ktxHeader->id[2] != 'T') || (ktxHeader->id[3] != 'X') || + (ktxHeader->id[4] != ' ') || (ktxHeader->id[5] != '1') || (ktxHeader->id[6] != '1')) + { + TRACELOG(LOG_WARNING, "IMAGE: KTX file data not valid"); + } + else + { + fileDataPtr += sizeof(KTXHeader); // Move file data pointer + + image.width = ktxHeader->width; + image.height = ktxHeader->height; + image.mipmaps = ktxHeader->mipmapLevels; + + TRACELOGD("IMAGE: KTX file data info:"); + TRACELOGD(" > Image width: %i", ktxHeader->width); + TRACELOGD(" > Image height: %i", ktxHeader->height); + TRACELOGD(" > Image format: 0x%x", ktxHeader->glInternalFormat); + + fileDataPtr += ktxHeader->keyValueDataSize; // Skip value data size + + int dataSize = ((int *)fileDataPtr)[0]; + fileDataPtr += sizeof(int); + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + if (ktxHeader->glInternalFormat == 0x8D64) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (ktxHeader->glInternalFormat == 0x9274) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (ktxHeader->glInternalFormat == 0x9278) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; + } + } + + return image; +} + +// Save image data as KTX file +// NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018) +static int SaveKTX(Image image, const char *fileName) +{ + // KTX file Header (64 bytes) + // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + // v2.0 - http://github.khronos.org/KTX-Specification/ - still on draft, not ready for implementation + typedef struct { + char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" // KTX 2.0: "«KTX 22»\r\n\x1A\n" + unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 + unsigned int glType; // For compressed textures, glType must equal 0 + unsigned int glTypeSize; // For compressed texture data, usually 1 + unsigned int glFormat; // For compressed textures is 0 + unsigned int glInternalFormat; // Compressed internal format + unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...) // KTX 2.0: UInt32 vkFormat + unsigned int width; // Texture image width in pixels + unsigned int height; // Texture image height in pixels + unsigned int depth; // For 2D textures is 0 + unsigned int elements; // Number of array elements, usually 0 + unsigned int faces; // Cubemap faces, for no-cubemap = 1 + unsigned int mipmapLevels; // Non-mipmapped textures = 1 + unsigned int keyValueDataSize; // Used to encode any arbitrary data... // KTX 2.0: UInt32 levelOrder - ordering of the mipmap levels, usually 0 + // KTX 2.0: UInt32 supercompressionScheme - 0 (None), 1 (Crunch CRN), 2 (Zlib DEFLATE)... + // KTX 2.0 defines additional header elements... + } KTXHeader; + + // Calculate file dataSize required + int dataSize = sizeof(KTXHeader); + + for (int i = 0, width = image.width, height = image.height; i < image.mipmaps; i++) + { + dataSize += GetPixelDataSize(width, height, image.format); + width /= 2; height /= 2; + } + + unsigned char *fileData = RL_CALLOC(dataSize, 1); + unsigned char *fileDataPtr = fileData; + + KTXHeader ktxHeader = { 0 }; + + // KTX identifier (v1.1) + //unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' }; + //unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + + const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' }; + + // Get the image header + memcpy(ktxHeader.id, ktxIdentifier, 12); // KTX 1.1 signature + ktxHeader.endianness = 0; + ktxHeader.glType = 0; // Obtained from image.format + ktxHeader.glTypeSize = 1; + ktxHeader.glFormat = 0; // Obtained from image.format + ktxHeader.glInternalFormat = 0; // Obtained from image.format + ktxHeader.glBaseInternalFormat = 0; + ktxHeader.width = image.width; + ktxHeader.height = image.height; + ktxHeader.depth = 0; + ktxHeader.elements = 0; + ktxHeader.faces = 1; + ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats) + ktxHeader.keyValueDataSize = 0; // No extra data after the header + + rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function + ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only + + // NOTE: We can save into a .ktx all PixelFormats supported by raylib, including compressed formats like DXT, ETC or ASTC + + if (ktxHeader.glFormat == -1) TRACELOG(LOG_WARNING, "IMAGE: GL format not supported for KTX export (%i)", ktxHeader.glFormat); + else + { + memcpy(fileDataPtr, &ktxHeader, sizeof(KTXHeader)); + fileDataPtr += sizeof(KTXHeader); + + int width = image.width; + int height = image.height; + int dataOffset = 0; + + // Save all mipmaps data + for (int i = 0; i < image.mipmaps; i++) + { + unsigned int dataSize = GetPixelDataSize(width, height, image.format); + + memcpy(fileDataPtr, &dataSize, sizeof(unsigned int)); + memcpy(fileDataPtr + 4, (unsigned char *)image.data + dataOffset, dataSize); + + width /= 2; + height /= 2; + dataOffset += dataSize; + fileDataPtr += (4 + dataSize); + } + } + + int success = SaveFileData(fileName, fileData, dataSize); + + RL_FREE(fileData); // Free file data buffer + + // If all data has been written correctly to file, success = 1 + return success; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_PVR) +// Loading PVR image data (uncompressed or PVRT compression) +// NOTE: PVR v2 not supported, use PVR v3 instead +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extension: + // GL_IMG_texture_compression_pvrtc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 + // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 + +#if 0 // Not used... + // PVR file v2 Header (52 bytes) + typedef struct { + unsigned int headerLength; + unsigned int height; + unsigned int width; + unsigned int numMipmaps; + unsigned int flags; + unsigned int dataLength; + unsigned int bpp; + unsigned int bitmaskRed; + unsigned int bitmaskGreen; + unsigned int bitmaskBlue; + unsigned int bitmaskAlpha; + unsigned int pvrTag; + unsigned int numSurfs; + } PVRHeaderV2; +#endif + + // PVR file v3 Header (52 bytes) + // NOTE: After it could be metadata (15 bytes?) + typedef struct { + char id[4]; + unsigned int flags; + unsigned char channels[4]; // pixelFormat high part + unsigned char channelDepth[4]; // pixelFormat low part + unsigned int colourSpace; + unsigned int channelType; + unsigned int height; + unsigned int width; + unsigned int depth; + unsigned int numSurfaces; + unsigned int numFaces; + unsigned int numMipmaps; + unsigned int metaDataSize; + } PVRHeaderV3; + +#if 0 // Not used... + // Metadata (usually 15 bytes) + typedef struct { + unsigned int devFOURCC; + unsigned int key; + unsigned int dataSize; // Not used? + unsigned char *data; // Not used? + } PVRMetadata; +#endif + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + // Check PVR image version + unsigned char pvrVersion = fileDataPtr[0]; + + // Load different PVR data formats + if (pvrVersion == 0x50) + { + PVRHeaderV3 *pvrHeader = (PVRHeaderV3 *)fileDataPtr; + + if ((pvrHeader->id[0] != 'P') || (pvrHeader->id[1] != 'V') || (pvrHeader->id[2] != 'R') || (pvrHeader->id[3] != 3)) + { + TRACELOG(LOG_WARNING, "IMAGE: PVR file data not valid"); + } + else + { + fileDataPtr += sizeof(PVRHeaderV3); // Skip header + + image.width = pvrHeader->width; + image.height = pvrHeader->height; + image.mipmaps = pvrHeader->numMipmaps; + + // Check data format + if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 0)) && (pvrHeader->channelDepth[0] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 'a')) && ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8))) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + else if ((pvrHeader->channels[0] == 'r') && (pvrHeader->channels[1] == 'g') && (pvrHeader->channels[2] == 'b')) + { + if (pvrHeader->channels[3] == 'a') + { + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 5) && (pvrHeader->channelDepth[2] == 5) && (pvrHeader->channelDepth[3] == 1)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((pvrHeader->channelDepth[0] == 4) && (pvrHeader->channelDepth[1] == 4) && (pvrHeader->channelDepth[2] == 4) && (pvrHeader->channelDepth[3] == 4)) image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8) && (pvrHeader->channelDepth[3] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } + else if (pvrHeader->channels[3] == 0) + { + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 6) && (pvrHeader->channelDepth[2] == 5)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + } + } + else if (pvrHeader->channels[0] == 2) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGB; + else if (pvrHeader->channels[0] == 3) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGBA; + + fileDataPtr += pvrHeader->metaDataSize; // Skip meta data header + + // Calculate data size (depends on format) + int bpp = 0; + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + default: break; + } + + int dataSize = image.width*image.height*bpp/8; // Total data size in bytes + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + } + } + else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: PVRv2 format not supported, update your files to PVRv3"); + } + + return image; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_ASTC) +// Load ASTC compressed image data (ASTC compression) +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + // Required extensions: + // GL_KHR_texture_compression_astc_hdr + // GL_KHR_texture_compression_astc_ldr + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 + // GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 + + // ASTC file Header (16 bytes) + typedef struct { + unsigned char id[4]; // Signature: 0x13 0xAB 0xA1 0x5C + unsigned char blockX; // Block X dimensions + unsigned char blockY; // Block Y dimensions + unsigned char blockZ; // Block Z dimensions (1 for 2D images) + unsigned char width[3]; // Image width in pixels (24bit value) + unsigned char height[3]; // Image height in pixels (24bit value) + unsigned char length[3]; // Image Z-size (1 for 2D images) + } ASTCHeader; + + Image image = { 0 }; + + if (fileDataPtr != NULL) + { + ASTCHeader *astcHeader = (ASTCHeader *)fileDataPtr; + + if ((astcHeader->id[3] != 0x5c) || (astcHeader->id[2] != 0xa1) || (astcHeader->id[1] != 0xab) || (astcHeader->id[0] != 0x13)) + { + TRACELOG(LOG_WARNING, "IMAGE: ASTC file data not valid"); + } + else + { + fileDataPtr += sizeof(ASTCHeader); // Skip header + + // NOTE: Assuming Little Endian (could it be wrong?) + image.width = 0x00000000 | ((int)astcHeader->width[2] << 16) | ((int)astcHeader->width[1] << 8) | ((int)astcHeader->width[0]); + image.height = 0x00000000 | ((int)astcHeader->height[2] << 16) | ((int)astcHeader->height[1] << 8) | ((int)astcHeader->height[0]); + + TRACELOGD("IMAGE: ASTC file data info:"); + TRACELOGD(" > Image width: %i", image.width); + TRACELOGD(" > Image height: %i", image.height); + TRACELOGD(" > Image blocks: %ix%i", astcHeader->blockX, astcHeader->blockY); + + image.mipmaps = 1; // NOTE: ASTC format only contains one mipmap level + + // NOTE: Each block is always stored in 128bit so we can calculate the bpp + int bpp = 128/(astcHeader->blockX*astcHeader->blockY); + + // NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8 + if ((bpp == 8) || (bpp == 2)) + { + int dataSize = image.width*image.height*bpp/8; // Data size in bytes + + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); + + memcpy(image.data, fileDataPtr, dataSize); + + if (bpp == 8) image.format = PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA; + else if (bpp == 2) image.format = PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA; + } + else TRACELOG(LOG_WARNING, "IMAGE: ASTC block size configuration not supported"); + } + } + + return image; +} +#endif diff --git a/raylib_pi4_test/utils.c b/raylib_pi4_test/utils.c new file mode 100644 index 0000000..66804fb --- /dev/null +++ b/raylib_pi4_test/utils.c @@ -0,0 +1,436 @@ +/********************************************************************************************** +* +* raylib.utils - Some common utility functions +* +* CONFIGURATION: +* +* #define SUPPORT_TRACELOG +* Show TraceLog() output messages +* NOTE: By default LOG_DEBUG traces not shown +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // WARNING: Required for: LogType enum + +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + +#include "utils.h" + +#if defined(PLATFORM_ANDROID) + #include // Required for: Android error types + #include // Required for: Android log system: __android_log_vprint() + #include // Required for: Android assets manager: AAsset, AAssetManager_open(), ... +#endif + +#include // Required for: exit() +#include // Required for: FILE, fopen(), fseek(), ftell(), fread(), fwrite(), fprintf(), vprintf(), fclose() +#include // Required for: va_list, va_start(), va_end() +#include // Required for: strcpy(), strcat() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_TRACELOG_MSG_LENGTH + #define MAX_TRACELOG_MSG_LENGTH 128 // Max length of one trace-log message +#endif +#ifndef MAX_UWP_MESSAGES + #define MAX_UWP_MESSAGES 512 // Max UWP messages to process +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static int logTypeLevel = LOG_INFO; // Minimum log type level + +static TraceLogCallback traceLog = NULL; // TraceLog callback function pointer +static LoadFileDataCallback loadFileData = NULL; // LoadFileData callback funtion pointer +static SaveFileDataCallback saveFileData = NULL; // SaveFileText callback funtion pointer +static LoadFileTextCallback loadFileText = NULL; // LoadFileText callback funtion pointer +static SaveFileTextCallback saveFileText = NULL; // SaveFileText callback funtion pointer + +//---------------------------------------------------------------------------------- +// Functions to set internal callbacks +//---------------------------------------------------------------------------------- +void SetTraceLogCallback(TraceLogCallback callback) { traceLog = callback; } // Set custom trace log +void SetLoadFileDataCallback(LoadFileDataCallback callback) { loadFileData = callback; } // Set custom file data loader +void SetSaveFileDataCallback(SaveFileDataCallback callback) { saveFileData = callback; } // Set custom file data saver +void SetLoadFileTextCallback(LoadFileTextCallback callback) { loadFileText = callback; } // Set custom file text loader +void SetSaveFileTextCallback(SaveFileTextCallback callback) { saveFileText = callback; } // Set custom file text saver + + +#if defined(PLATFORM_ANDROID) +static AAssetManager *assetManager = NULL; // Android assets manager pointer +static const char *internalDataPath = NULL; // Android internal data path +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) +FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); + +static int android_read(void *cookie, char *buf, int size); +static int android_write(void *cookie, const char *buf, int size); +static fpos_t android_seek(void *cookie, fpos_t offset, int whence); +static int android_close(void *cookie); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utilities +//---------------------------------------------------------------------------------- + +// Set the current threshold (minimum) log level +void SetTraceLogLevel(int logType) { logTypeLevel = logType; } + +// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +void TraceLog(int logType, const char *text, ...) +{ +#if defined(SUPPORT_TRACELOG) + // Message has level below current threshold, don't emit + if (logType < logTypeLevel) return; + + va_list args; + va_start(args, text); + + if (traceLog) + { + traceLog(logType, text, args); + va_end(args); + return; + } + +#if defined(PLATFORM_ANDROID) + switch(logType) + { + case LOG_TRACE: __android_log_vprint(ANDROID_LOG_VERBOSE, "raylib", text, args); break; + case LOG_DEBUG: __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", text, args); break; + case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib", text, args); break; + case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib", text, args); break; + case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib", text, args); break; + case LOG_FATAL: __android_log_vprint(ANDROID_LOG_FATAL, "raylib", text, args); break; + default: break; + } +#else + char buffer[MAX_TRACELOG_MSG_LENGTH] = { 0 }; + + switch (logType) + { + case LOG_TRACE: strcpy(buffer, "TRACE: "); break; + case LOG_DEBUG: strcpy(buffer, "DEBUG: "); break; + case LOG_INFO: strcpy(buffer, "INFO: "); break; + case LOG_WARNING: strcpy(buffer, "WARNING: "); break; + case LOG_ERROR: strcpy(buffer, "ERROR: "); break; + case LOG_FATAL: strcpy(buffer, "FATAL: "); break; + default: break; + } + + strcat(buffer, text); + strcat(buffer, "\n"); + vprintf(buffer, args); +#endif + + va_end(args); + + if (logType == LOG_ERROR) exit(1); // If error, exit program + +#endif // SUPPORT_TRACELOG +} + +// Internal memory allocator +// NOTE: Initializes to zero by default +void *MemAlloc(int size) +{ + void *ptr = RL_CALLOC(size, 1); + return ptr; +} + +// Internal memory reallocator +void *MemRealloc(void *ptr, int size) +{ + void *ret = RL_REALLOC(ptr, size); + return ret; +} + +// Internal memory free +void MemFree(void *ptr) +{ + RL_FREE(ptr); +} + +// Load data from file into a buffer +unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) +{ + unsigned char *data = NULL; + *bytesRead = 0; + + if (fileName != NULL) + { + if (loadFileData) + { + data = loadFileData(fileName, bytesRead); + return data; + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); + + // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] + unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file); + *bytesRead = count; + + if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Unload file data allocated by LoadFileData() +void UnloadFileData(unsigned char *data) +{ + RL_FREE(data); +} + +// Save data to file from buffer +bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) +{ + bool success = false; + + if (fileName != NULL) + { + if (saveFileData) + { + return saveFileData(fileName, data, bytesToWrite); + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), bytesToWrite, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != bytesToWrite) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + int result = fclose(file); + if (result == 0) success = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; +} + +// Load text data from file, returns a '\0' terminated string +// NOTE: text chars array should be freed manually +char *LoadFileText(const char *fileName) +{ + char *text = NULL; + + if (fileName != NULL) + { + if (loadFileText) + { + text = loadFileText(fileName); + return text; + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "rt"); + + if (file != NULL) + { + // WARNING: When reading a file as 'text' file, + // text mode causes carriage return-linefeed translation... + // ...but using fseek() should return correct byte-offset + fseek(file, 0, SEEK_END); + unsigned int size = (unsigned int)ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + text = (char *)RL_MALLOC((size + 1)*sizeof(char)); + unsigned int count = (unsigned int)fread(text, sizeof(char), size, file); + + // WARNING: \r\n is converted to \n on reading, so, + // read bytes count gets reduced by the number of lines + if (count < size) text = RL_REALLOC(text, count + 1); + + // Zero-terminate the string + text[count] = '\0'; + + TRACELOG(LOG_INFO, "FILEIO: [%s] Text file loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return text; +} + +// Unload file text data allocated by LoadFileText() +void UnloadFileText(unsigned char *text) +{ + RL_FREE(text); +} + +// Save text data to file (write), string must be '\0' terminated +bool SaveFileText(const char *fileName, char *text) +{ + bool success = false; + + if (fileName != NULL) + { + if (saveFileText) + { + return saveFileText(fileName, text); + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = fprintf(file, "%s", text); + + if (count < 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + int result = fclose(file); + if (result == 0) success = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; +} + +#if defined(PLATFORM_ANDROID) +// Initialize asset manager from android app +void InitAssetManager(AAssetManager *manager, const char *dataPath) +{ + assetManager = manager; + internalDataPath = dataPath; +} + +// Replacement for fopen() +// Ref: https://developer.android.com/ndk/reference/group/asset +FILE *android_fopen(const char *fileName, const char *mode) +{ + if (mode[0] == 'w') + { + // TODO: fopen() is mapped to android_fopen() that only grants read access + // to assets directory through AAssetManager but we want to also be able to + // write data when required using the standard stdio FILE access functions + // Ref: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only + #undef fopen + return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); + #define fopen(name, mode) android_fopen(name, mode) + } + else + { + // NOTE: AAsset provides access to read-only asset + AAsset *asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_UNKNOWN); + + if (asset != NULL) + { + // Return pointer to file in the assets + return funopen(asset, android_read, android_write, android_seek, android_close); + } + else + { + #undef fopen + // Just do a regular open if file is not found in the assets + return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); + #define fopen(name, mode) android_fopen(name, mode) + } + } +} +#endif // PLATFORM_ANDROID + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) +static int android_read(void *cookie, char *buf, int size) +{ + return AAsset_read((AAsset *)cookie, buf, size); +} + +static int android_write(void *cookie, const char *buf, int size) +{ + TRACELOG(LOG_WARNING, "ANDROID: Failed to provide write access to APK"); + + return EACCES; +} + +static fpos_t android_seek(void *cookie, fpos_t offset, int whence) +{ + return AAsset_seek((AAsset *)cookie, offset, whence); +} + +static int android_close(void *cookie) +{ + AAsset_close((AAsset *)cookie); + return 0; +} +#endif // PLATFORM_ANDROID diff --git a/raylib_pi4_test/utils.h b/raylib_pi4_test/utils.h new file mode 100644 index 0000000..3d7a379 --- /dev/null +++ b/raylib_pi4_test/utils.h @@ -0,0 +1,79 @@ +/********************************************************************************************** +* +* raylib.utils - Some common utility functions +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef UTILS_H +#define UTILS_H + +#if defined(PLATFORM_ANDROID) + #include // Required for: FILE + #include // Required for: AAssetManager +#endif + +#if defined(SUPPORT_TRACELOG) + #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) + + #if defined(SUPPORT_TRACELOG_DEBUG) + #define TRACELOGD(...) TraceLog(LOG_DEBUG, __VA_ARGS__) + #else + #define TRACELOGD(...) (void)0 + #endif +#else + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) + #define fopen(name, mode) android_fopen(name, mode) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// Nop... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(PLATFORM_ANDROID) +void InitAssetManager(AAssetManager *manager, const char *dataPath); // Initialize asset manager from android app +FILE *android_fopen(const char *fileName, const char *mode); // Replacement for fopen() -> Read-only! +#endif + +#ifdef __cplusplus +} +#endif + +#endif // UTILS_H diff --git a/raylib_pi4_test/uwp_events.h b/raylib_pi4_test/uwp_events.h new file mode 100644 index 0000000..2c403fd --- /dev/null +++ b/raylib_pi4_test/uwp_events.h @@ -0,0 +1,119 @@ +/********************************************************************************************** +* +* raylib.uwp_events - Functions for bootstrapping UWP functionality within raylib's core. +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2020-2020 Reece Mackie (@Rover656) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef UWP_EVENTS_H +#define UWP_EVENTS_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(PLATFORM_UWP) + +// Determine if UWP functions are set and ready for raylib's use. +bool UWPIsConfigured(); + +// Call this to set the UWP data path you wish for saving and loading. +void UWPSetDataPath(const char* path); + +// Function for getting program time. +typedef double(*UWPQueryTimeFunc)(); +UWPQueryTimeFunc UWPGetQueryTimeFunc(void); +void UWPSetQueryTimeFunc(UWPQueryTimeFunc func); + +// Function for sleeping the current thread +typedef void (*UWPSleepFunc)(double sleepUntil); +UWPSleepFunc UWPGetSleepFunc(void); +void UWPSetSleepFunc(UWPSleepFunc func); + +// Function for querying the display size +typedef void(*UWPDisplaySizeFunc)(int* width, int* height); +UWPDisplaySizeFunc UWPGetDisplaySizeFunc(void); +void UWPSetDisplaySizeFunc(UWPDisplaySizeFunc func); + +// Functions for mouse cursor control +typedef void(*UWPMouseFunc)(void); +UWPMouseFunc UWPGetMouseLockFunc(); +void UWPSetMouseLockFunc(UWPMouseFunc func); +UWPMouseFunc UWPGetMouseUnlockFunc(); +void UWPSetMouseUnlockFunc(UWPMouseFunc func); +UWPMouseFunc UWPGetMouseShowFunc(); +void UWPSetMouseShowFunc(UWPMouseFunc func); +UWPMouseFunc UWPGetMouseHideFunc(); +void UWPSetMouseHideFunc(UWPMouseFunc func); + +// Function for setting mouse cursor position. +typedef void (*UWPMouseSetPosFunc)(int x, int y); +UWPMouseSetPosFunc UWPGetMouseSetPosFunc(); +void UWPSetMouseSetPosFunc(UWPMouseSetPosFunc func); + +// The below functions are implemented in core.c but are placed here so they can be called by user code. +// This choice is made as platform-specific code is preferred to be kept away from raylib.h + +// Call this when a Key is pressed or released. +void UWPKeyDownEvent(int key, bool down, bool controlKey); + +// Call this on the CoreWindow::CharacterRecieved event +void UWPKeyCharEvent(int key); + +// Call when a mouse button state changes +void UWPMouseButtonEvent(int button, bool down); + +// Call when the mouse cursor moves +void UWPMousePosEvent(double x, double y); + +// Call when the mouse wheel moves +void UWPMouseWheelEvent(int deltaY); + +// Call when the window resizes +void UWPResizeEvent(int width, int height); + +// Call when a gamepad is made active +void UWPActivateGamepadEvent(int gamepad, bool active); + +// Call when a gamepad button state changes +void UWPRegisterGamepadButton(int gamepad, int button, bool down); + +// Call when a gamepad axis state changes +void UWPRegisterGamepadAxis(int gamepad, int axis, float value); + +// Call when the touch point moves +void UWPGestureMove(int pointer, float x, float y); + +// Call when there is a touch down or up +void UWPGestureTouch(int pointer, float x, float y, bool touch); + +// Set the core window pointer so that we can pass it to EGL. +void* UWPGetCoreWindowPtr(); +void UWPSetCoreWindowPtr(void* ptr); + +#if defined(__cplusplus) +} +#endif + +#endif // PLATFORM_UWP + +#endif // UWP_EVENTS_H diff --git a/rtl/make.bat b/rtl/make.bat index 40da913..7a9b9ac 100644 --- a/rtl/make.bat +++ b/rtl/make.bat @@ -7,7 +7,7 @@ if %errorlevel% neq 0 GOTO ERRORCOMPILE %quartus_bin_path%\quartus_cpf -c -q 100KHz -g 3.3 -n p output_files\pistorm.pof bitstream.svf if %errorlevel% neq 0 GOTO ERRORSVF -echo y | pscp -l pi -pw raspberry -P 22 bitstream.svf %piaddress%:./pistorm/bitstream.svf +echo y | pscp -l pi -pw raspberry -P 22 bitstream.svf %piaddress%:./pistorm/rtl/bitstream.svf if %errorlevel% neq 0 GOTO ERRORSCP echo y | plink -l pi -pw raspberry -P 22 %piaddress% "cd pistorm && ./nprog.sh"