2013-12-19 09:50:37 +08:00
|
|
|
/******************************************************************************
|
2014-10-15 11:23:02 +08:00
|
|
|
* Spine Runtimes Software License
|
2015-07-21 01:39:34 +08:00
|
|
|
* Version 2.3
|
2013-12-19 09:50:37 +08:00
|
|
|
*
|
2015-07-21 01:39:34 +08:00
|
|
|
* Copyright (c) 2013-2015, Esoteric Software
|
2013-04-24 13:57:34 +08:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
2014-10-15 11:23:02 +08:00
|
|
|
* You are granted a perpetual, non-exclusive, non-sublicensable and
|
2015-07-21 01:39:34 +08:00
|
|
|
* non-transferable license to use, install, execute and perform the Spine
|
|
|
|
* Runtimes Software (the "Software") and derivative works solely for personal
|
|
|
|
* or internal use. Without the written permission of Esoteric Software (see
|
|
|
|
* Section 2 of the Spine Software License Agreement), you may not (a) modify,
|
|
|
|
* translate, adapt or otherwise create derivative works, improvements of the
|
|
|
|
* Software or develop new applications using the Software or (b) remove,
|
|
|
|
* delete, alter or obscure any trademarks or any copyright, trademark, patent
|
|
|
|
* or other intellectual property or proprietary rights notices on or in the
|
|
|
|
* Software, including any copy thereof. Redistributions in binary or source
|
|
|
|
* form must include this license and terms.
|
2013-04-24 13:57:34 +08:00
|
|
|
*
|
2014-10-15 11:23:02 +08:00
|
|
|
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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
|
2015-07-21 01:39:34 +08:00
|
|
|
* EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
2014-10-15 11:23:02 +08:00
|
|
|
* 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.
|
2013-12-19 09:50:37 +08:00
|
|
|
*****************************************************************************/
|
2013-04-24 13:57:34 +08:00
|
|
|
|
|
|
|
#include <spine/Atlas.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <spine/extension.h>
|
|
|
|
|
2014-10-15 11:23:02 +08:00
|
|
|
spAtlasPage* spAtlasPage_create (spAtlas* atlas, const char* name) {
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlasPage* self = NEW(spAtlasPage);
|
2014-10-15 11:23:02 +08:00
|
|
|
CONST_CAST(spAtlas*, self->atlas) = atlas;
|
2013-04-24 13:57:34 +08:00
|
|
|
MALLOC_STR(self->name, name);
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
void spAtlasPage_dispose (spAtlasPage* self) {
|
|
|
|
_spAtlasPage_disposeTexture(self);
|
2013-06-04 00:52:32 +08:00
|
|
|
FREE(self->name);
|
2013-06-02 22:26:46 +08:00
|
|
|
FREE(self);
|
2013-04-24 13:57:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**/
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlasRegion* spAtlasRegion_create () {
|
2014-10-15 11:23:02 +08:00
|
|
|
return NEW(spAtlasRegion);
|
2013-04-24 13:57:34 +08:00
|
|
|
}
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
void spAtlasRegion_dispose (spAtlasRegion* self) {
|
2013-04-24 13:57:34 +08:00
|
|
|
FREE(self->name);
|
|
|
|
FREE(self->splits);
|
|
|
|
FREE(self->pads);
|
|
|
|
FREE(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**/
|
|
|
|
|
2013-06-02 22:26:46 +08:00
|
|
|
typedef struct {
|
2013-04-24 13:57:34 +08:00
|
|
|
const char* begin;
|
|
|
|
const char* end;
|
|
|
|
} Str;
|
|
|
|
|
|
|
|
static void trim (Str* str) {
|
|
|
|
while (isspace(*str->begin) && str->begin < str->end)
|
|
|
|
(str->begin)++;
|
|
|
|
if (str->begin == str->end) return;
|
|
|
|
str->end--;
|
|
|
|
while (isspace(*str->end) && str->end >= str->begin)
|
|
|
|
str->end--;
|
|
|
|
str->end++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Tokenize string without modification. Returns 0 on failure. */
|
|
|
|
static int readLine (const char* begin, const char* end, Str* str) {
|
|
|
|
static const char* nextStart;
|
|
|
|
if (begin) {
|
|
|
|
nextStart = begin;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (nextStart == end) return 0;
|
|
|
|
str->begin = nextStart;
|
|
|
|
|
|
|
|
/* Find next delimiter. */
|
|
|
|
while (nextStart != end && *nextStart != '\n')
|
|
|
|
nextStart++;
|
|
|
|
|
|
|
|
str->end = nextStart;
|
|
|
|
trim(str);
|
|
|
|
|
|
|
|
if (nextStart != end) nextStart++;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-09-22 16:08:23 +08:00
|
|
|
/* Moves str->begin past the first occurrence of c. Returns 0 on failure. */
|
2013-04-24 13:57:34 +08:00
|
|
|
static int beginPast (Str* str, char c) {
|
|
|
|
const char* begin = str->begin;
|
|
|
|
while (1) {
|
|
|
|
char lastSkippedChar = *begin;
|
|
|
|
if (begin == str->end) return 0;
|
|
|
|
begin++;
|
|
|
|
if (lastSkippedChar == c) break;
|
|
|
|
}
|
|
|
|
str->begin = begin;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns 0 on failure. */
|
|
|
|
static int readValue (const char* end, Str* str) {
|
|
|
|
readLine(0, end, str);
|
|
|
|
if (!beginPast(str, ':')) return 0;
|
|
|
|
trim(str);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-10-15 11:23:02 +08:00
|
|
|
/* Returns the number of tuple values read (1, 2, 4, or 0 for failure). */
|
2013-04-24 13:57:34 +08:00
|
|
|
static int readTuple (const char* end, Str tuple[]) {
|
2013-06-02 22:26:46 +08:00
|
|
|
int i;
|
2015-04-14 15:23:16 +08:00
|
|
|
Str str = {NULL, NULL};
|
2013-04-24 13:57:34 +08:00
|
|
|
readLine(0, end, &str);
|
|
|
|
if (!beginPast(&str, ':')) return 0;
|
2013-06-02 22:26:46 +08:00
|
|
|
|
2013-04-24 13:57:34 +08:00
|
|
|
for (i = 0; i < 3; ++i) {
|
|
|
|
tuple[i].begin = str.begin;
|
2014-10-15 11:23:02 +08:00
|
|
|
if (!beginPast(&str, ',')) break;
|
2013-04-24 13:57:34 +08:00
|
|
|
tuple[i].end = str.begin - 2;
|
|
|
|
trim(&tuple[i]);
|
|
|
|
}
|
|
|
|
tuple[i].begin = str.begin;
|
|
|
|
tuple[i].end = str.end;
|
|
|
|
trim(&tuple[i]);
|
|
|
|
return i + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* mallocString (Str* str) {
|
2014-10-15 11:23:02 +08:00
|
|
|
int length = (int)(str->end - str->begin);
|
2013-04-24 13:57:34 +08:00
|
|
|
char* string = MALLOC(char, length + 1);
|
|
|
|
memcpy(string, str->begin, length);
|
|
|
|
string[length] = '\0';
|
|
|
|
return string;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int indexOf (const char** array, int count, Str* str) {
|
2014-10-15 11:23:02 +08:00
|
|
|
int length = (int)(str->end - str->begin);
|
2013-04-24 13:57:34 +08:00
|
|
|
int i;
|
|
|
|
for (i = count - 1; i >= 0; i--)
|
|
|
|
if (strncmp(array[i], str->begin, length) == 0) return i;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int equals (Str* str, const char* other) {
|
|
|
|
return strncmp(other, str->begin, str->end - str->begin) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int toInt (Str* str) {
|
2014-10-15 11:23:02 +08:00
|
|
|
return (int)strtol(str->begin, (char**)&str->end, 10);
|
2013-04-24 13:57:34 +08:00
|
|
|
}
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
static spAtlas* abortAtlas (spAtlas* self) {
|
|
|
|
spAtlas_dispose(self);
|
2013-04-24 13:57:34 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char* formatNames[] = {"Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888", "RGBA8888"};
|
|
|
|
static const char* textureFilterNames[] = {"Nearest", "Linear", "MipMap", "MipMapNearestNearest", "MipMapLinearNearest",
|
|
|
|
"MipMapNearestLinear", "MipMapLinearLinear"};
|
|
|
|
|
2014-10-15 11:23:02 +08:00
|
|
|
spAtlas* spAtlas_create (const char* begin, int length, const char* dir, void* rendererObject) {
|
|
|
|
spAtlas* self;
|
|
|
|
|
2013-06-02 22:26:46 +08:00
|
|
|
int count;
|
2013-04-24 13:57:34 +08:00
|
|
|
const char* end = begin + length;
|
2014-10-15 11:23:02 +08:00
|
|
|
int dirLength = (int)strlen(dir);
|
2013-04-24 13:57:34 +08:00
|
|
|
int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\';
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlasPage *page = 0;
|
|
|
|
spAtlasPage *lastPage = 0;
|
|
|
|
spAtlasRegion *lastRegion = 0;
|
2013-04-24 13:57:34 +08:00
|
|
|
Str str;
|
|
|
|
Str tuple[4];
|
2014-10-15 11:23:02 +08:00
|
|
|
|
|
|
|
self = NEW(spAtlas);
|
|
|
|
self->rendererObject = rendererObject;
|
|
|
|
|
2013-04-24 13:57:34 +08:00
|
|
|
readLine(begin, 0, 0);
|
|
|
|
while (readLine(0, end, &str)) {
|
|
|
|
if (str.end - str.begin == 0) {
|
|
|
|
page = 0;
|
|
|
|
} else if (!page) {
|
|
|
|
char* name = mallocString(&str);
|
|
|
|
char* path = MALLOC(char, dirLength + needsSlash + strlen(name) + 1);
|
|
|
|
memcpy(path, dir, dirLength);
|
|
|
|
if (needsSlash) path[dirLength] = '/';
|
|
|
|
strcpy(path + dirLength + needsSlash, name);
|
|
|
|
|
2014-10-15 11:23:02 +08:00
|
|
|
page = spAtlasPage_create(self, name);
|
2013-04-24 13:57:34 +08:00
|
|
|
FREE(name);
|
|
|
|
if (lastPage)
|
|
|
|
lastPage->next = page;
|
|
|
|
else
|
|
|
|
self->pages = page;
|
|
|
|
lastPage = page;
|
|
|
|
|
2014-10-15 11:23:02 +08:00
|
|
|
switch (readTuple(end, tuple)) {
|
|
|
|
case 0:
|
|
|
|
return abortAtlas(self);
|
|
|
|
case 2: /* size is only optional for an atlas packed with an old TexturePacker. */
|
|
|
|
page->width = toInt(tuple);
|
|
|
|
page->height = toInt(tuple + 1);
|
|
|
|
if (!readTuple(end, tuple)) return abortAtlas(self);
|
|
|
|
}
|
|
|
|
page->format = (spAtlasFormat)indexOf(formatNames, 7, tuple);
|
2013-04-24 13:57:34 +08:00
|
|
|
|
|
|
|
if (!readTuple(end, tuple)) return abortAtlas(self);
|
2013-12-19 09:50:37 +08:00
|
|
|
page->minFilter = (spAtlasFilter)indexOf(textureFilterNames, 7, tuple);
|
|
|
|
page->magFilter = (spAtlasFilter)indexOf(textureFilterNames, 7, tuple + 1);
|
2013-04-24 13:57:34 +08:00
|
|
|
|
|
|
|
if (!readValue(end, &str)) return abortAtlas(self);
|
|
|
|
if (!equals(&str, "none")) {
|
2014-10-15 11:23:02 +08:00
|
|
|
page->uWrap = *str.begin == 'x' ? SP_ATLAS_REPEAT : (*str.begin == 'y' ? SP_ATLAS_CLAMPTOEDGE : SP_ATLAS_REPEAT);
|
|
|
|
page->vWrap = *str.begin == 'x' ? SP_ATLAS_CLAMPTOEDGE : (*str.begin == 'y' ? SP_ATLAS_REPEAT : SP_ATLAS_REPEAT);
|
2013-04-24 13:57:34 +08:00
|
|
|
}
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
_spAtlasPage_createTexture(page, path);
|
2013-04-24 13:57:34 +08:00
|
|
|
FREE(path);
|
|
|
|
} else {
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlasRegion *region = spAtlasRegion_create();
|
2013-04-24 13:57:34 +08:00
|
|
|
if (lastRegion)
|
|
|
|
lastRegion->next = region;
|
|
|
|
else
|
|
|
|
self->regions = region;
|
|
|
|
lastRegion = region;
|
|
|
|
|
|
|
|
region->page = page;
|
|
|
|
region->name = mallocString(&str);
|
|
|
|
|
|
|
|
if (!readValue(end, &str)) return abortAtlas(self);
|
|
|
|
region->rotate = equals(&str, "true");
|
|
|
|
|
|
|
|
if (readTuple(end, tuple) != 2) return abortAtlas(self);
|
|
|
|
region->x = toInt(tuple);
|
|
|
|
region->y = toInt(tuple + 1);
|
|
|
|
|
|
|
|
if (readTuple(end, tuple) != 2) return abortAtlas(self);
|
|
|
|
region->width = toInt(tuple);
|
|
|
|
region->height = toInt(tuple + 1);
|
|
|
|
|
|
|
|
region->u = region->x / (float)page->width;
|
|
|
|
region->v = region->y / (float)page->height;
|
2013-06-02 22:26:46 +08:00
|
|
|
if (region->rotate) {
|
|
|
|
region->u2 = (region->x + region->height) / (float)page->width;
|
|
|
|
region->v2 = (region->y + region->width) / (float)page->height;
|
|
|
|
} else {
|
|
|
|
region->u2 = (region->x + region->width) / (float)page->width;
|
|
|
|
region->v2 = (region->y + region->height) / (float)page->height;
|
|
|
|
}
|
2013-04-24 13:57:34 +08:00
|
|
|
|
|
|
|
if (!(count = readTuple(end, tuple))) return abortAtlas(self);
|
|
|
|
if (count == 4) { /* split is optional */
|
|
|
|
region->splits = MALLOC(int, 4);
|
|
|
|
region->splits[0] = toInt(tuple);
|
|
|
|
region->splits[1] = toInt(tuple + 1);
|
|
|
|
region->splits[2] = toInt(tuple + 2);
|
|
|
|
region->splits[3] = toInt(tuple + 3);
|
|
|
|
|
|
|
|
if (!(count = readTuple(end, tuple))) return abortAtlas(self);
|
|
|
|
if (count == 4) { /* pad is optional, but only present with splits */
|
|
|
|
region->pads = MALLOC(int, 4);
|
|
|
|
region->pads[0] = toInt(tuple);
|
|
|
|
region->pads[1] = toInt(tuple + 1);
|
|
|
|
region->pads[2] = toInt(tuple + 2);
|
|
|
|
region->pads[3] = toInt(tuple + 3);
|
|
|
|
|
|
|
|
if (!readTuple(end, tuple)) return abortAtlas(self);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
region->originalWidth = toInt(tuple);
|
|
|
|
region->originalHeight = toInt(tuple + 1);
|
|
|
|
|
|
|
|
readTuple(end, tuple);
|
2013-06-02 22:26:46 +08:00
|
|
|
region->offsetX = toInt(tuple);
|
|
|
|
region->offsetY = toInt(tuple + 1);
|
2013-04-24 13:57:34 +08:00
|
|
|
|
|
|
|
if (!readValue(end, &str)) return abortAtlas(self);
|
|
|
|
region->index = toInt(&str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2014-10-15 11:23:02 +08:00
|
|
|
spAtlas* spAtlas_createFromFile (const char* path, void* rendererObject) {
|
|
|
|
int dirLength;
|
2013-06-02 22:26:46 +08:00
|
|
|
char *dir;
|
2013-12-19 09:50:37 +08:00
|
|
|
int length;
|
2013-06-02 22:26:46 +08:00
|
|
|
const char* data;
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlas* atlas = 0;
|
2013-04-24 13:57:34 +08:00
|
|
|
|
|
|
|
/* Get directory from atlas path. */
|
|
|
|
const char* lastForwardSlash = strrchr(path, '/');
|
|
|
|
const char* lastBackwardSlash = strrchr(path, '\\');
|
|
|
|
const char* lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
|
|
|
|
if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
|
2014-10-15 11:23:02 +08:00
|
|
|
dirLength = (int)(lastSlash ? lastSlash - path : 0);
|
2013-06-02 22:26:46 +08:00
|
|
|
dir = MALLOC(char, dirLength + 1);
|
2013-04-24 13:57:34 +08:00
|
|
|
memcpy(dir, path, dirLength);
|
|
|
|
dir[dirLength] = '\0';
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
data = _spUtil_readFile(path, &length);
|
2014-10-15 11:23:02 +08:00
|
|
|
if (data) atlas = spAtlas_create(data, length, dir, rendererObject);
|
2013-04-24 13:57:34 +08:00
|
|
|
|
2013-11-12 10:09:47 +08:00
|
|
|
FREE(data);
|
2013-04-24 13:57:34 +08:00
|
|
|
FREE(dir);
|
|
|
|
return atlas;
|
|
|
|
}
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
void spAtlas_dispose (spAtlas* self) {
|
|
|
|
spAtlasRegion* region, *nextRegion;
|
|
|
|
spAtlasPage* page = self->pages;
|
2013-04-24 13:57:34 +08:00
|
|
|
while (page) {
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlasPage* nextPage = page->next;
|
|
|
|
spAtlasPage_dispose(page);
|
2013-04-24 13:57:34 +08:00
|
|
|
page = nextPage;
|
|
|
|
}
|
|
|
|
|
2013-06-02 22:26:46 +08:00
|
|
|
region = self->regions;
|
2013-04-24 13:57:34 +08:00
|
|
|
while (region) {
|
2013-06-02 22:26:46 +08:00
|
|
|
nextRegion = region->next;
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlasRegion_dispose(region);
|
2013-04-24 13:57:34 +08:00
|
|
|
region = nextRegion;
|
|
|
|
}
|
|
|
|
|
|
|
|
FREE(self);
|
|
|
|
}
|
|
|
|
|
2013-12-19 09:50:37 +08:00
|
|
|
spAtlasRegion* spAtlas_findRegion (const spAtlas* self, const char* name) {
|
|
|
|
spAtlasRegion* region = self->regions;
|
2013-04-24 13:57:34 +08:00
|
|
|
while (region) {
|
|
|
|
if (strcmp(region->name, name) == 0) return region;
|
|
|
|
region = region->next;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|