clean up gfx a bit... more to come

This commit is contained in:
asrael 2025-10-05 17:19:59 -05:00
parent c662c550df
commit 7f56e0bfe8
2 changed files with 195 additions and 61 deletions

View file

@ -45,9 +45,7 @@
(when (pxl8.key_pressed "9") (when (pxl8.key_pressed "9")
(set use-nes-palette (not use-nes-palette)) (set use-nes-palette (not use-nes-palette))
(local palette-path (if use-nes-palette "palettes/nes.ase" "sprites/pxl8_logo.ase")) (local palette-path (if use-nes-palette "palettes/nes.ase" "sprites/pxl8_logo.ase"))
(print (.. "Switching to palette: " palette-path)) (pxl8.load_palette palette-path))
(pxl8.load_palette palette-path)
(print "Palette loaded"))
(case current-effect (case current-effect
1 (do 1 (do

View file

@ -82,7 +82,13 @@ struct pxl8_gfx {
bool affine_textures; bool affine_textures;
}; };
static pxl8_skyline_fit pxl8_skyline_find_position(const pxl8_skyline* skyline, u32 atlas_w, u32 atlas_h, u32 rect_w, u32 rect_h) { static pxl8_skyline_fit pxl8_skyline_find_position(
const pxl8_skyline* skyline,
u32 atlas_w,
u32 atlas_h,
u32 rect_w,
u32 rect_h
) {
pxl8_skyline_fit result = {.found = false}; pxl8_skyline_fit result = {.found = false};
i32 best_y = INT32_MAX; i32 best_y = INT32_MAX;
i32 best_x = 0; i32 best_x = 0;
@ -129,8 +135,6 @@ static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w,
} }
} }
pxl8_skyline_node new_node = {pos.x, pos.y + (i32)h, (i32)w};
u32 nodes_to_remove = 0; u32 nodes_to_remove = 0;
for (u32 i = node_idx; i < skyline->count; i++) { for (u32 i = node_idx; i < skyline->count; i++) {
if (skyline->nodes[i].x < pos.x + (i32)w) { if (skyline->nodes[i].x < pos.x + (i32)w) {
@ -142,15 +146,21 @@ static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w,
if (skyline->count - nodes_to_remove + 1 > skyline->capacity) { if (skyline->count - nodes_to_remove + 1 > skyline->capacity) {
skyline->capacity = (skyline->count - nodes_to_remove + 1) * 2; skyline->capacity = (skyline->count - nodes_to_remove + 1) * 2;
skyline->nodes = (pxl8_skyline_node*)SDL_realloc(skyline->nodes, skyline->capacity * sizeof(pxl8_skyline_node)); skyline->nodes = (pxl8_skyline_node*)SDL_realloc(
skyline->nodes,
skyline->capacity * sizeof(pxl8_skyline_node)
);
} }
if (nodes_to_remove > 0) { if (nodes_to_remove > 0) {
SDL_memmove(&skyline->nodes[node_idx + 1], &skyline->nodes[node_idx + nodes_to_remove], SDL_memmove(
(skyline->count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node)); &skyline->nodes[node_idx + 1],
&skyline->nodes[node_idx + nodes_to_remove],
(skyline->count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node)
);
} }
skyline->nodes[node_idx] = new_node; skyline->nodes[node_idx] = (pxl8_skyline_node){pos.x, pos.y + (i32)h, (i32)w};
skyline->count = skyline->count - nodes_to_remove + 1; skyline->count = skyline->count - nodes_to_remove + 1;
} }
@ -158,8 +168,11 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) {
for (u32 i = 0; i < skyline->count - 1; ) { for (u32 i = 0; i < skyline->count - 1; ) {
if (skyline->nodes[i].y == skyline->nodes[i + 1].y) { if (skyline->nodes[i].y == skyline->nodes[i + 1].y) {
skyline->nodes[i].width += skyline->nodes[i + 1].width; skyline->nodes[i].width += skyline->nodes[i + 1].width;
SDL_memmove(&skyline->nodes[i + 1], &skyline->nodes[i + 2], SDL_memmove(
(skyline->count - i - 2) * sizeof(pxl8_skyline_node)); &skyline->nodes[i + 1],
&skyline->nodes[i + 2],
(skyline->count - i - 2) * sizeof(pxl8_skyline_node)
);
skyline->count--; skyline->count--;
} else { } else {
i++; i++;
@ -167,7 +180,12 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) {
} }
} }
static pxl8_atlas* pxl8_atlas_create(SDL_Renderer* renderer, u32 width, u32 height, pxl8_color_mode color_mode) { static pxl8_atlas* pxl8_atlas_create(
SDL_Renderer* renderer,
u32 width,
u32 height,
pxl8_color_mode color_mode
) {
pxl8_atlas* atlas = (pxl8_atlas*)SDL_calloc(1, sizeof(pxl8_atlas)); pxl8_atlas* atlas = (pxl8_atlas*)SDL_calloc(1, sizeof(pxl8_atlas));
if (!atlas) return NULL; if (!atlas) return NULL;
@ -181,8 +199,14 @@ static pxl8_atlas* pxl8_atlas_create(SDL_Renderer* renderer, u32 width, u32 heig
return NULL; return NULL;
} }
atlas->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, atlas->texture = SDL_CreateTexture(
SDL_TEXTUREACCESS_STREAMING, width, height); renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
width,
height
);
if (!atlas->texture) { if (!atlas->texture) {
SDL_free(atlas->pixels); SDL_free(atlas->pixels);
SDL_free(atlas); SDL_free(atlas);
@ -209,7 +233,8 @@ static pxl8_atlas* pxl8_atlas_create(SDL_Renderer* renderer, u32 width, u32 heig
} }
atlas->skyline.capacity = 16; atlas->skyline.capacity = 16;
atlas->skyline.nodes = (pxl8_skyline_node*)SDL_calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node)); atlas->skyline.nodes =
(pxl8_skyline_node*)SDL_calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
if (!atlas->skyline.nodes) { if (!atlas->skyline.nodes) {
SDL_free(atlas->free_list); SDL_free(atlas->free_list);
SDL_free(atlas->entries); SDL_free(atlas->entries);
@ -228,15 +253,19 @@ static pxl8_atlas* pxl8_atlas_create(SDL_Renderer* renderer, u32 width, u32 heig
static void pxl8_atlas_destroy(pxl8_atlas* atlas) { static void pxl8_atlas_destroy(pxl8_atlas* atlas) {
if (!atlas) return; if (!atlas) return;
SDL_free(atlas->free_list);
SDL_free(atlas->entries); SDL_free(atlas->entries);
SDL_free(atlas->free_list);
SDL_free(atlas->pixels);
SDL_free(atlas->skyline.nodes); SDL_free(atlas->skyline.nodes);
if (atlas->texture) SDL_DestroyTexture(atlas->texture); if (atlas->texture) SDL_DestroyTexture(atlas->texture);
SDL_free(atlas->pixels);
SDL_free(atlas); SDL_free(atlas);
} }
static bool pxl8_atlas_expand(pxl8_atlas* atlas, SDL_Renderer* renderer, pxl8_color_mode color_mode) { static bool pxl8_atlas_expand(
pxl8_atlas* atlas,
SDL_Renderer* renderer,
pxl8_color_mode color_mode
) {
if (!atlas || atlas->width >= 4096) return false; if (!atlas || atlas->width >= 4096) return false;
u32 new_size = atlas->width * 2; u32 new_size = atlas->width * 2;
@ -246,8 +275,14 @@ static bool pxl8_atlas_expand(pxl8_atlas* atlas, SDL_Renderer* renderer, pxl8_co
u8* new_pixels = (u8*)SDL_calloc(new_size * new_size, bytes_per_pixel); u8* new_pixels = (u8*)SDL_calloc(new_size * new_size, bytes_per_pixel);
if (!new_pixels) return false; if (!new_pixels) return false;
SDL_Texture* new_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_Texture* new_texture = SDL_CreateTexture(
SDL_TEXTUREACCESS_STREAMING, new_size, new_size); renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
new_size,
new_size
);
if (!new_texture) { if (!new_texture) {
SDL_free(new_pixels); SDL_free(new_pixels);
return false; return false;
@ -268,8 +303,14 @@ static bool pxl8_atlas_expand(pxl8_atlas* atlas, SDL_Renderer* renderer, pxl8_co
for (u32 i = 0; i < atlas->entry_count; i++) { for (u32 i = 0; i < atlas->entry_count; i++) {
if (!atlas->entries[i].active) continue; if (!atlas->entries[i].active) continue;
pxl8_skyline_fit fit = pxl8_skyline_find_position(&new_skyline, new_size, new_size, pxl8_skyline_fit fit = pxl8_skyline_find_position(
atlas->entries[i].w, atlas->entries[i].h); &new_skyline,
new_size,
new_size,
atlas->entries[i].w,
atlas->entries[i].h
);
if (!fit.found) { if (!fit.found) {
SDL_free(new_skyline.nodes); SDL_free(new_skyline.nodes);
SDL_DestroyTexture(new_texture); SDL_DestroyTexture(new_texture);
@ -311,15 +352,27 @@ static bool pxl8_atlas_expand(pxl8_atlas* atlas, SDL_Renderer* renderer, pxl8_co
return true; return true;
} }
static u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, const char* path, pxl8_color_mode color_mode) { static u32 pxl8_atlas_add_texture(
pxl8_atlas* atlas,
const u8* pixels,
u32 w,
u32 h,
const char* path,
pxl8_color_mode color_mode
) {
if (!atlas || !pixels) return UINT32_MAX; if (!atlas || !pixels) return UINT32_MAX;
pxl8_skyline_fit fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); pxl8_skyline_fit fit =
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
if (!fit.found) { if (!fit.found) {
if (!pxl8_atlas_expand(atlas, atlas->texture ? SDL_GetRendererFromTexture(atlas->texture) : NULL, color_mode)) { SDL_Renderer* renderer = atlas->texture ? SDL_GetRendererFromTexture(atlas->texture) : NULL;
if (!pxl8_atlas_expand(atlas, renderer, color_mode)) {
return UINT32_MAX; return UINT32_MAX;
} }
fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
if (!fit.found) return UINT32_MAX; if (!fit.found) return UINT32_MAX;
} }
@ -329,8 +382,10 @@ static u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u3
} else { } else {
if (atlas->entry_count >= atlas->entry_capacity) { if (atlas->entry_count >= atlas->entry_capacity) {
atlas->entry_capacity *= 2; atlas->entry_capacity *= 2;
atlas->entries = (pxl8_atlas_entry*)SDL_realloc(atlas->entries, atlas->entries = (pxl8_atlas_entry*)SDL_realloc(
atlas->entry_capacity * sizeof(pxl8_atlas_entry)); atlas->entries,
atlas->entry_capacity * sizeof(pxl8_atlas_entry)
);
} }
texture_id = atlas->entry_count++; texture_id = atlas->entry_count++;
} }
@ -449,7 +504,13 @@ u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) {
return gfx ? gfx->palette_size : 0; return gfx ? gfx->palette_size : 0;
} }
pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height) { pxl8_gfx* pxl8_gfx_create(
pxl8_color_mode mode,
pxl8_resolution resolution,
const char* title,
i32 window_width,
i32 window_height
) {
pxl8_gfx* gfx = (pxl8_gfx*)SDL_calloc(1, sizeof(*gfx)); pxl8_gfx* gfx = (pxl8_gfx*)SDL_calloc(1, sizeof(*gfx));
if (!gfx) { if (!gfx) {
pxl8_error("Failed to allocate graphics context"); pxl8_error("Failed to allocate graphics context");
@ -457,7 +518,11 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons
} }
gfx->color_mode = mode; gfx->color_mode = mode;
pxl8_gfx_get_resolution_dimensions(resolution, &gfx->framebuffer_width, &gfx->framebuffer_height); pxl8_gfx_get_resolution_dimensions(
resolution,
&gfx->framebuffer_width,
&gfx->framebuffer_height
);
gfx->window = SDL_CreateWindow( gfx->window = SDL_CreateWindow(
title, title,
@ -478,10 +543,12 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons
return NULL; return NULL;
} }
SDL_SetRenderLogicalPresentation(gfx->renderer, SDL_SetRenderLogicalPresentation(
gfx->framebuffer_width, gfx->renderer,
gfx->framebuffer_height, gfx->framebuffer_width,
SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); gfx->framebuffer_height,
SDL_LOGICAL_PRESENTATION_INTEGER_SCALE
);
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel; i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
@ -579,16 +646,19 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width,
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY; if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
} }
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, NULL, gfx->color_mode); u32 texture_id =
pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, NULL, gfx->color_mode);
if (texture_id == UINT32_MAX) { if (texture_id == UINT32_MAX) {
pxl8_error("Texture doesn't fit in atlas"); pxl8_error("Texture doesn't fit in atlas");
return PXL8_ERROR_INVALID_SIZE; return PXL8_ERROR_INVALID_SIZE;
} }
pxl8_debug("Created texture %u: %ux%u at (%d,%d)", pxl8_debug(
texture_id, width, height, "Created texture %u: %ux%u at (%d,%d)",
gfx->atlas->entries[texture_id].x, texture_id, width, height,
gfx->atlas->entries[texture_id].y); gfx->atlas->entries[texture_id].x,
gfx->atlas->entries[texture_id].y
);
return texture_id; return texture_id;
} }
@ -623,7 +693,14 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
u32 sprite_w = ase_file.header.width; u32 sprite_w = ase_file.header.width;
u32 sprite_h = ase_file.header.height; u32 sprite_h = ase_file.header.height;
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, ase_file.frames[0].pixels, sprite_w, sprite_h, path, gfx->color_mode); u32 texture_id = pxl8_atlas_add_texture(
gfx->atlas,
ase_file.frames[0].pixels,
sprite_w,
sprite_h,
path,
gfx->color_mode
);
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
@ -659,7 +736,10 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
u32 copy_size = (ase_file.palette.entry_count < gfx->palette_size) ? ase_file.palette.entry_count : gfx->palette_size; u32 copy_size = ase_file.palette.entry_count < gfx->palette_size
? ase_file.palette.entry_count
: gfx->palette_size;
memcpy(gfx->palette, ase_file.palette.colors, copy_size * sizeof(u32)); memcpy(gfx->palette, ase_file.palette.colors, copy_size * sizeof(u32));
u32 last_color = (copy_size > 0) ? gfx->palette[copy_size - 1] : 0xFF000000; u32 last_color = (copy_size > 0) ? gfx->palette[copy_size - 1] : 0xFF000000;
@ -678,7 +758,15 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
return PXL8_OK; return PXL8_OK;
} }
static void pxl8_upload_indexed_texture(SDL_Texture* texture, const u8* indexed, const u32* palette, u32 palette_size, i32 width, i32 height, u32 default_color) { static void pxl8_upload_indexed_texture(
SDL_Texture* texture,
const u8* indexed,
const u32* palette,
u32 palette_size,
i32 width,
i32 height,
u32 default_color
) {
static u32* rgba_buffer = NULL; static u32* rgba_buffer = NULL;
static size_t buffer_size = 0; static size_t buffer_size = 0;
size_t needed_size = width * height; size_t needed_size = width * height;
@ -702,13 +790,22 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return; if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, gfx->framebuffer, SDL_UpdateTexture(
gfx->framebuffer_width * 4); gfx->framebuffer_texture,
NULL,
gfx->framebuffer,
gfx->framebuffer_width * 4
);
} else { } else {
pxl8_upload_indexed_texture(gfx->framebuffer_texture, gfx->framebuffer, pxl8_upload_indexed_texture(
gfx->palette, gfx->palette_size, gfx->framebuffer_texture,
gfx->framebuffer_width, gfx->framebuffer_height, gfx->framebuffer,
0xFF000000); gfx->palette,
gfx->palette_size,
gfx->framebuffer_width,
gfx->framebuffer_height,
0xFF000000
);
} }
} }
@ -716,13 +813,22 @@ void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->atlas || !gfx->atlas->texture || !gfx->atlas->dirty) return; if (!gfx || !gfx->initialized || !gfx->atlas || !gfx->atlas->texture || !gfx->atlas->dirty) return;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
SDL_UpdateTexture(gfx->atlas->texture, NULL, gfx->atlas->pixels, SDL_UpdateTexture(
gfx->atlas->width * 4); gfx->atlas->texture,
NULL,
gfx->atlas->pixels,
gfx->atlas->width * 4
);
} else { } else {
pxl8_upload_indexed_texture(gfx->atlas->texture, gfx->atlas->pixels, pxl8_upload_indexed_texture(
gfx->palette, gfx->palette_size, gfx->atlas->texture,
gfx->atlas->width, gfx->atlas->height, gfx->atlas->pixels,
0x00000000); gfx->palette,
gfx->palette_size,
gfx->atlas->width,
gfx->atlas->height,
0x00000000
);
} }
gfx->atlas->dirty = false; gfx->atlas->dirty = false;
@ -976,11 +1082,21 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
const u8* sprite_data = gfx->atlas->pixels + entry->y * gfx->atlas->width + entry->x; const u8* sprite_data = gfx->atlas->pixels + entry->y * gfx->atlas->width + entry->x;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
pxl8_blit_simd_hicolor((u32*)gfx->framebuffer, gfx->framebuffer_width, pxl8_blit_simd_hicolor(
(const u32*)sprite_data, gfx->atlas->width, x, y, w, h); (u32*)gfx->framebuffer,
gfx->framebuffer_width,
(const u32*)sprite_data,
gfx->atlas->width,
x, y, w, h
);
} else { } else {
pxl8_blit_simd_indexed(gfx->framebuffer, gfx->framebuffer_width, pxl8_blit_simd_indexed(
sprite_data, gfx->atlas->width, x, y, w, h); gfx->framebuffer,
gfx->framebuffer_width,
sprite_data,
gfx->atlas->width,
x, y, w, h
);
} }
} else { } else {
for (i32 py = 0; py < draw_height; py++) { for (i32 py = 0; py < draw_height; py++) {
@ -1051,7 +1167,14 @@ void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 ta
} }
} }
void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t) { void pxl8_gfx_interpolate_palettes(
pxl8_gfx* gfx,
u32* palette1,
u32* palette2,
u8 start,
u8 count,
f32 t
) {
if (!gfx || !gfx->palette || !palette1 || !palette2 || count == 0) return; if (!gfx || !gfx->palette || !palette1 || !palette2 || count == 0) return;
if (t < 0.0f) t = 0.0f; if (t < 0.0f) t = 0.0f;
@ -1496,7 +1619,19 @@ static void pxl8_draw_flat_top_triangle_textured(
} }
} }
void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id) { void pxl8_3d_draw_triangle_textured(
pxl8_gfx* gfx,
pxl8_vec3 v0,
pxl8_vec3 v1,
pxl8_vec3 v2,
f32 u0,
f32 v0f,
f32 u1,
f32 v1f,
f32 u2,
f32 v2f,
u32 texture_id
) {
if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return;
pxl8_vec4 cv0 = pxl8_transform_vertex(gfx, v0); pxl8_vec4 cv0 = pxl8_transform_vertex(gfx, v0);
@ -1526,7 +1661,8 @@ void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, p
tv[2].w = cv2.w; tv[2].w = cv2.w;
if (gfx->backface_culling) { if (gfx->backface_culling) {
i32 cross = (tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x); i32 cross =
(tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x);
if (cross >= 0) return; if (cross >= 0) return;
} }