Cute Exporter»Blog
Clay Murray
Odin is a great programming language. It lives up to at least this part of its mission statement.

joy of programming


I hadn't done any low level programming for a while before I started using Odin. Now I can't get enough.

Odin, while great, does have its rough edges. You can expect that from a newer language with a smaller community. And because the creator [Ginger Bill](https://twitter.com/TheGingerBill) develops on a Windows machine there's a few more rough edges in MacOS vs Windows. I am going to talk about one of the issues I ran into on Mac and how I solved it.

(Look I develop on MacOS. I come from a web background and like the OS and it's ecosystem. Sue me.)

OSX Calling Convention Bug


Related Issue

I am not sure of the root problem, but Odin isn't passing certain structs properly. I think it has to do with the MacOS ABI wanting them passed as a 128 bit vector, but again I'm not sure.

This appears to be a major issue and it is, however we can work around it.
In my code and tool I use Raylib []. Odin's foreign system is awesome, making it super easy to include anything compiled to C code. I link to the raylib .a file and done... In a perfect world. As mentioned, the calling convention is broken for a few structs. But as mentioned, there is a solution. It's a simple solution. We can pass in our structs normally and modify our calls to pass in a pointer parameter for output instead of directly returning.


1
RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Returns the screen space position for a 2d camera world space position



Becomes


1
RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera, Vector2 *out); // Returns the screen space position for a 2d camera world space position



The above method I was doing this a not smart way. I was modifying the entire raylib source to make these changes to the calls. This worked fine. I didn't think much of it as I was building out Cute Exporter. Then it came time to start making sure it worked for Windows. Windows doesn't have these calling convention issues. Do I now need to maintain two different versions of raylib for MacOS and Windows solely to work around Odin? That could work but doesn't seem optimal. Then I realized, Odin has a great foreign system we can do this better.

Instead of modifying raylib directly I made a small wrapper C library around functions that needed to change. I don't even need to do it for every raylib function, only the broken ones.
And in Windows we can import the raylib library code and be done.

In the C wrapper


1
2
3
4
5
6
7
8
#include "raylib.h"
Vector2 raylibext_GetScreenToWorld2D(Vector2 vec, Camera2D cam, Vector2 *input, Vector2 *result) {
	Vector2 res = GetScreenToWorld2D(*input, cam);

	*result = res;

	return res;
}




I can then import my wrapper functions and work with them to have the same Odin calling convention in Windows or MacOS. (Odin also has a great system for OS specific code.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//import_darwin.odin
//The _darwin part means this file only gets included and compiled when on a Darwin (MacOS) system.
when ODIN_OS == "darwin"{
	foreign import raylib_host {
		"libraylib.a",
		"raylib_extended.o",
	}
};
foreign raylib_host {
	@(link_name="raylibext_GetScreenToWorld2D")
	_GetScreenToWorld2D :: proc(raylib._Vector2, raylib.Camera2D, ^raylib._Vector2, ^raylib._Vector2) -> raylib._Vector2 ---;
}

GetScreenToWorld2D :: proc "c" (coord: raylib._Vector2, camera: raylib.Camera2D) -> raylib._Vector2 {
		result : raylib._Vector2;
		input := raylib._Vector2{coord.x, coord.y};
		_GetScreenToWorld2D(input, camera, &input, &result);
		return raylib._Vector2{result.x, result.y};
}


The advantage of this method being, I don't need to maintain a different branch of my changes to raylib. If a newer version or bug fix come out I don't need to worry about conflicts.

In the end it's nothing too complex or crazy and that is fully owed to how flexible and awesome Odin is.


Clay Murray
Cute Exporter has been exporting the metadata about the texture atlas as a json file. This was because it's easy to do, and I know json better than other file formats.
But, there are game engines that don't use json. I want Cute Exporter to support those use cases.

My goals in supporting this use case are:

  • Easy setup.
  • Let user do what they want.
  • User provided file extension.
  • Easy to change.


Using a template file.



One solution may be to have a template format. You would create a template and direct Cute Exporter to fill it in. This works fine, it's a perfectly valid solution. For basic use cases this is all you need. However, if you are wanting to do anything more advanced, either you can't, or you have to write an extension to the template engine in another programing language. For Example, Handlebars allows you to add in custom filters and functions to your template written in javascript.

This is easy to set up for the user and easy to change. It fails in user provided file extension. Let user do what they want is a 50/50 here. They can do what they want, as long as it's not advanced.

Now we could extend our scheme to have a definition file written in json or XML. The definition would provide the path to the template file and the file extension. Not bad, but now the user has 3 different files and file formats.

Additionally, there is the issue of where do these file get saved? The files could be saved to a known location on disk. Doing this makes it easy for the program to find and list all different export formats they support along with user provided ones.

I want any user provided data to be located nearby the project file. I want the user to be able to version control their code, images, and exporter tools in the same place. If I want multiple people to work on the project with me, I have to pass along the custom export format in addition to the code and instruct them how to install it.

Lua scripting



The way I decided to solve this was to give the user a programming language, Lua. The user can use any template format or techniques they want. All Cute Exporter cares about is returning a string that it will save to a file.

How it works



In the settings the user will tell Cute Exporter it wants to use a custom exporter and provides a path to the script to run. Cute Exporter saves the path to this file relative to the directory the project file is saved in. With the intention that the user will save the exporter script with the rest of their code and assets. That way if you want other people to work with you, they don't need to worry about any setup with Cute Exporter. Download Cute Exporter and open the project file.

When it comes time to export the data, Cute Exporter will run the custom script. To standardize things it runs a function in the script named `doExport` passing in the metadata for the export. It expects a string to be returned. The string returned will be saved to the data file.

What about file extension? The great thing about Lua is it's easy to use for configuration in addition to scripting. In keeping with making it simple the user can optionally provide a global variable declaring the file extension. Cute Exporter will run the script and read the global variable `outputFileExt` if it exists. The string value in that variable is used as the file extension for the custom export format.

I think this hits all my goals. It's easy to setup with a few clicks in the settings. Lua is a full fledged programming language, the user should have no trouble doing what they want. The user can provide a file extension. And the script is easy to change. On top of that, we expect one file only for the user to provide config info about their format. Lua can include other Lua files and code and this implementation allows that, giving the custom format access to any code written by others with a simple import statement.

Small example.



Let's wrap this up. Here's a minimum example of an export format.

1
2
3
4
outputFileExt = "custom"
function doExport(data) 
	return "hello!"
end


The resulting data file will have the extension `.custom` and the text `hello!` in it. A non trivial format should do something with the data provided but as shown, Cute Exporter only cares about the return value.
Clay Murray
Hello, I suspect not to many people read these posts but for those who do you may be wondering where I was. Well it’s been a busy and stressful last few months for my job and then I started playing minecraft and just played that way too much. However, I’m feeling better and I threw my gaming PC out the window now I only have time for making cool stuff again. I think I left Cute Exporter in a pretty good spot before my little hiatus, but it should be receiving some cool features soon. A big one on my list is anchor points. I don’t know if that is the proper term but they will be spots you can place on your assets that are tagged with some value. This will be useful for alignment of your assets or specifying where a part of the asset is each frame in an animation. For instance if you have a character swinging a sword you can mark the hand with a tag in each frame so you can align the sword with the rest of the animation!
Clay Murray
The next version of Cute Exporter should be 2.5 here is what you should be able to expect in this version.
  • A few builtin preset export formats
    • JSON
    • INI
    • Suggest one if you’d like to see it

  • Set the framerate of an animation
  • Playback the animation at a framerate in the GUI
  • Define regions in a PNG to export. Useful for asset packs that already exist as a texture atlas
  • Improvements to showing the folder structure of parent assets in the GUI
  • Automatically create an animation from a PSD or PSD group.
  • Define custom anchor points / rects on a layer that can be tagged with info.
    • Useful for defining different points on a character sprite like baseline or hands.
  • Showing more group information of the layers from the parent file.
  • Max texture size and outputting of multiple texture atlas if exceeding that size.
  • Per layer trim alpha setting.
  • Per asset trim alpha setting.
Clay Murray
Hello this is 2.1 of Cute Exporter!


Few small changes in the app. No new major features.

  • Support Aseprite files saved as color indexed files.
  • Support Aseprite files saved as grayscale.
  • Support add PNG files to your project.
    • Useful in cases where you already only have PNGs but also want to pack them as well.

  • Ctrl/Cmd + s now saves the project.
  • Ability to remove files from project
  • Refresh asset from disk.
  • Toggle trim alpha setting
  • Export ASE cel’s location in parent document.
    • Useful in cases where you trim alpha around your asset and export them as individual cels. You now have the data to reconstruct the asset in your engine from individual parts.
  • Little popups to inform that a save succeeded.
  • Bug fixes
  • Better memory management