From e0d29e8f89e601e1f77d1c7d2fb1c7d83e3185a1 Mon Sep 17 00:00:00 2001 From: rexim Date: Thu, 12 Sep 2024 02:01:17 +0700 Subject: [PATCH] Move Sprite rendering to C3 --- client.c3 | 211 +++++++++++++++++++++++----------------------------- client.mjs | 2 +- client.mts | 19 +---- client.wasm | Bin 17545 -> 19587 bytes common.c3 | 1 + 5 files changed, 99 insertions(+), 134 deletions(-) diff --git a/client.c3 b/client.c3 index 1d16794..780bd60 100644 --- a/client.c3 +++ b/client.c3 @@ -1,4 +1,5 @@ import std::math; +import std::sort; import common; const float NEAR_CLIPPING_PLANE = 0.1; @@ -25,8 +26,8 @@ struct Sprite { Vector2 position; float z; float scale; - Vector2 crop_position; - Vector2 crop_size; + IVector2 crop_position; + IVector2 crop_size; float dist; // Actual distance. float pdist; // Perpendicular distance. @@ -36,12 +37,12 @@ struct Sprite { struct SpritePool { Sprite[SPRITE_POOL_CAPACITY] items; int length; - Sprite[SPRITE_POOL_CAPACITY] visible_items; + Sprite*[SPRITE_POOL_CAPACITY] visible_items; int visible_length; } -fn Sprite *allocate_sprite_pool() @extern("allocate_sprite_pool") @wasm { - return mem::new(Sprite); +fn SpritePool *allocate_sprite_pool() @extern("allocate_sprite_pool") @wasm { + return mem::new(SpritePool); } fn void reset_sprite_pool(SpritePool *sprite_pool) @extern("reset_sprite_pool") @wasm { @@ -333,127 +334,103 @@ fn void render_minimap(Color *display, int display_width, int display_height, // ctx.restore(); } -// TODO: @translate fn void cull_and_sort_sprites(float camera_position_x, float camera_position_y, float camera_direction, SpritePool *sprite_pool) @extern("cull_and_sort_sprites") @wasm { - // const sp = new Vector2(); - // const dir = new Vector2().setPolar(camera.direction); - // const fov = camera.fovRight.clone().sub(camera.fovLeft); - - // visibleSprites.length = 0; - // for (let i = 0; i < spritePool.length; ++i) { - // const sprite = spritePool.items[i]; - - // sp.copy(sprite.position).sub(camera.position); - // const spl = sp.length(); - // if (spl <= NEAR_CLIPPING_PLANE) continue; // Sprite is too close - // if (spl >= FAR_CLIPPING_PLANE) continue; // Sprite is too far - - // const cos = sp.dot(dir)/spl; - // // TODO: @perf the sprites that are invisible on the screen but within FOV 180° are not culled - // // It may or may not impact the performance of renderSprites() - // if (cos < 0) continue; // Sprite is outside of the maximal FOV 180° - // sprite.dist = NEAR_CLIPPING_PLANE/cos; - // sp.norm().scale(sprite.dist).add(camera.position).sub(camera.fovLeft); - // sprite.t = sp.length()/fov.length()*Math.sign(sp.dot(fov)); - // sprite.pdist = sprite.position.clone().sub(camera.position).dot(dir); - - // // TODO: I'm not sure if these checks are necessary considering the `spl <= NEAR_CLIPPING_PLANE` above - // if (sprite.pdist < NEAR_CLIPPING_PLANE) continue; - // if (sprite.pdist >= FAR_CLIPPING_PLANE) continue; - - // visibleSprites.push(sprite); - // } + Camera camera = { .position = {camera_position_x, camera_position_y}, .direction = camera_direction }; + camera.update(); - // visibleSprites.sort((a, b) => b.pdist - a.pdist); + Vector2 dir = from_polar(camera_direction, 1.0f); + Vector2 fov = camera.fovRight - camera.fovLeft; + + sprite_pool.visible_length = 0; + for (int i = 0; i < sprite_pool.length; ++i) { + Sprite *sprite = &sprite_pool.items[i]; + + Vector2 sp = sprite.position - camera.position; + float spl = sp.length(); + if (spl <= NEAR_CLIPPING_PLANE) continue; // Sprite is too close + if (spl >= FAR_CLIPPING_PLANE) continue; // Sprite is too far + + float cos = sp.dot(dir)/spl; + // TODO: @perf the sprites that are invisible on the screen but within FOV 180° are not culled + // It may or may not impact the performance of renderSprites() + if (cos < 0) continue; // Sprite is outside of the maximal FOV 180° + sprite.dist = NEAR_CLIPPING_PLANE/cos; + sp = (sp.normalize()*sprite.dist) + camera.position - camera.fovLeft; + sprite.t = sp.length()/fov.length()*math::copysign(1.0f, sp.dot(fov)); + sprite.pdist = (sprite.position - camera.position).dot(dir); + + // TODO: I'm not sure if these checks are necessary considering the `spl <= NEAR_CLIPPING_PLANE` above + if (sprite.pdist < NEAR_CLIPPING_PLANE) continue; + if (sprite.pdist >= FAR_CLIPPING_PLANE) continue; + + sprite_pool.visible_items[sprite_pool.visible_length++] = sprite; + } + + quicksort(sprite_pool.visible_items[0..sprite_pool.visible_length-1], + fn int(Sprite *a, Sprite *b) => (int)math::copysign(1.0f, b.pdist - a.pdist)); } -// TODO: @translate fn void push_sprite(SpritePool *sprite_pool, Color *image_pixels, int image_width, int image_height, float x, float y, float z, float scale, - float crop_position_x, float crop_position_y, - float crop_size_x, float crop_size_y) @extern("push_sprite") @wasm { - // if (spritePool.length >= spritePool.items.length) { - // spritePool.items.push({ - // image, - // position: new Vector2(), - // z, - // scale, - // pdist: 0, - // dist: 0, - // t: 0, - // cropPosition: new Vector2(), - // cropSize: new Vector2(), - // }) - // } - - // const last = spritePool.length; - - // spritePool.items[last].image = image; - // spritePool.items[last].position.copy(position); - // spritePool.items[last].z = z; - // spritePool.items[last].scale = scale; - // spritePool.items[last].pdist = 0; - // spritePool.items[last].dist = 0; - // spritePool.items[last].t = 0; - - // if (image instanceof WasmImage) { - // if (cropPosition === undefined) { - // spritePool.items[last].cropPosition.set(0, 0); - // } else { - // spritePool.items[last].cropPosition.copy(cropPosition); - // } - // if (cropSize === undefined) { - // spritePool.items[last] - // .cropSize - // .set(image.width, image.height) - // .sub(spritePool.items[last].cropPosition); - // } else { - // spritePool.items[last].cropSize.copy(cropSize); - // } - // } else { - // spritePool.items[last].cropPosition.set(0, 0); - // spritePool.items[last].cropSize.set(0, 0); - // } - - // spritePool.length += 1; + int crop_position_x, int crop_position_y, + int crop_size_x, int crop_size_y) @extern("push_sprite") @wasm { + if (sprite_pool.length >= SPRITE_POOL_CAPACITY) return; + + usz last = sprite_pool.length; + + sprite_pool.items[last].image.pixels = image_pixels; + sprite_pool.items[last].image.width = image_width; + sprite_pool.items[last].image.height = image_height; + sprite_pool.items[last].position.x = x; + sprite_pool.items[last].position.y = y; + sprite_pool.items[last].z = z; + sprite_pool.items[last].scale = scale; + sprite_pool.items[last].pdist = 0; + sprite_pool.items[last].dist = 0; + sprite_pool.items[last].t = 0; + sprite_pool.items[last].crop_position.x = crop_position_x; + sprite_pool.items[last].crop_position.y = crop_position_y; + sprite_pool.items[last].crop_size.x = crop_size_x; + sprite_pool.items[last].crop_size.y = crop_size_y; + + sprite_pool.length += 1; } -// TODO: @translate -fn void render_sprites(Color *display, int display_width, int display_height, SpritePool *sprite_pool) @extern("render_sprites") @wasm { - // const backImageData = new Uint8ClampedArray(wasmClient.memory.buffer, display.backImagePtr, display.backImageWidth*display.backImageHeight*4); - // const zBuffer = new Float32Array(wasmClient.memory.buffer, display.zBufferPtr, display.backImageWidth); - // for (let sprite of sprites) { - // const cx = display.backImageWidth*sprite.t; - // const cy = display.backImageHeight*0.5; - // const maxSpriteSize = display.backImageHeight/sprite.pdist; - // const spriteSize = maxSpriteSize*sprite.scale; - // const x1 = Math.floor(cx - spriteSize*0.5); - // const x2 = Math.floor(x1 + spriteSize - 1); - // const bx1 = Math.max(0, x1); - // const bx2 = Math.min(display.backImageWidth-1, x2); - // const y1 = Math.floor(cy + maxSpriteSize*0.5 - maxSpriteSize*sprite.z); - // const y2 = Math.floor(y1 + spriteSize - 1); - // const by1 = Math.max(0, y1); - // const by2 = Math.min(display.backImageHeight-1, y2); - - // const src = new Uint8ClampedArray(wasmClient.memory.buffer, sprite.image.ptr, sprite.image.width*sprite.image.height*4); - // const dest = backImageData; - // for (let x = bx1; x <= bx2; ++x) { - // if (sprite.pdist < zBuffer[x]) { - // for (let y = by1; y <= by2; ++y) { - // const tx = Math.floor((x - x1)/spriteSize*sprite.cropSize.x); - // const ty = Math.floor((y - y1)/spriteSize*sprite.cropSize.y); - // const srcP = ((ty + sprite.cropPosition.y)*sprite.image.width + (tx + sprite.cropPosition.x))*4; - // const destP = (y*display.backImageWidth + x)*4; - // const alpha = src[srcP + 3]/255; - // dest[destP + 0] = dest[destP + 0]*(1 - alpha) + src[srcP + 0]*alpha; - // dest[destP + 1] = dest[destP + 1]*(1 - alpha) + src[srcP + 1]*alpha; - // dest[destP + 2] = dest[destP + 2]*(1 - alpha) + src[srcP + 2]*alpha; - // } - // } - // } - // } +fn void render_sprites(Color *display, int display_width, int display_height, float *zbuffer, + SpritePool *sprite_pool) @extern("render_sprites") @wasm { + for (int i = 0; i < sprite_pool.visible_length; ++i) { + Sprite *sprite = sprite_pool.visible_items[i]; + float cx = display_width*sprite.t; + float cy = display_height*0.5f; + float maxSpriteSize = display_height/sprite.pdist; + float spriteSize = maxSpriteSize*sprite.scale; + int x1 = (int)math::floor(cx - spriteSize*0.5f); + int x2 = (int)math::floor(x1 + spriteSize - 1.0f); + int bx1 = math::max(0, x1); + int bx2 = math::min(display_width-1, x2); + int y1 = (int)math::floor(cy + maxSpriteSize*0.5f - maxSpriteSize*sprite.z); + int y2 = (int)math::floor(y1 + spriteSize - 1); + int by1 = math::max(0, y1); + int by2 = math::min(display_height-1, y2); + + Color *src = sprite.image.pixels; + Color *dest = display; + for (int x = bx1; x <= bx2; ++x) { + if (sprite.pdist < zbuffer[x]) { + for (int y = by1; y <= by2; ++y) { + int tx = (int)math::floor((float)(x - x1)/spriteSize*sprite.crop_size.x); + int ty = (int)math::floor((float)(y - y1)/spriteSize*sprite.crop_size.y); + int srcP = (ty + sprite.crop_position.y)*sprite.image.width + (tx + sprite.crop_position.x); + int destP = y*display_width + x; + float alpha = src[srcP].a/255.0f; + dest[destP].r = (char)(dest[destP].r*(1 - alpha) + src[srcP].r*alpha); + dest[destP].g = (char)(dest[destP].g*(1 - alpha) + src[srcP].g*alpha); + dest[destP].b = (char)(dest[destP].b*(1 - alpha) + src[srcP].b*alpha); + } + } + } + } } diff --git a/client.mjs b/client.mjs index 3b12347..5c0c0c5 100644 --- a/client.mjs +++ b/client.mjs @@ -100,7 +100,7 @@ function cullAndSortSprites(wasmClient, camera, spritePool) { wasmClient.cull_and_sort_sprites(camera.position.x, camera.position.y, camera.direction, spritePool.ptr); } function renderSprites(display, wasmClient, spritePool) { - wasmClient.render_sprites(display.backImagePtr, display.backImageWidth, display.backImageHeight, spritePool.ptr); + wasmClient.render_sprites(display.backImagePtr, display.backImageWidth, display.backImageHeight, display.zBufferPtr, spritePool.ptr); } function pushSprite(wasmClient, spritePool, image, position, z, scale, cropPosition, cropSize) { const cropPosition1 = new Vector2(); diff --git a/client.mts b/client.mts index 071a462..a19d866 100644 --- a/client.mts +++ b/client.mts @@ -135,7 +135,7 @@ interface WasmClient extends common.WasmCommon { scale: number, crop_position_x: number, crop_position_y: number, crop_size_x: number, crop_size_y: number) => void; - render_sprites: (display: number, display_width: number, display_height: number, sprite_pool: number) => void, + render_sprites: (display: number, display_width: number, display_height: number, zbuffer: number, sprite_pool: number) => void, } function createDisplay(ctx: CanvasRenderingContext2D, wasmClient: WasmClient, backImageWidth: number, backImageHeight: number): Display { @@ -167,25 +167,12 @@ function displaySwapBackImageData(display: Display, wasmClient: WasmClient) { display.ctx.drawImage(display.backCtx.canvas, 0, 0, display.ctx.canvas.width, display.ctx.canvas.height); } -interface Sprite { - image: WasmImage; - position: Vector2; - z: number; - scale: number; - cropPosition: Vector2; - cropSize: Vector2; - - dist: number; // Actual distance. - pdist: number; // Perpendicular distance. - t: number; // Normalized horizontal position on the screen -} - function cullAndSortSprites(wasmClient: WasmClient, camera: Camera, spritePool: SpritePool) { wasmClient.cull_and_sort_sprites(camera.position.x, camera.position.y, camera.direction, spritePool.ptr); } function renderSprites(display: Display, wasmClient: WasmClient, spritePool: SpritePool) { - wasmClient.render_sprites(display.backImagePtr, display.backImageWidth, display.backImageHeight, spritePool.ptr) + wasmClient.render_sprites(display.backImagePtr, display.backImageWidth, display.backImageHeight, display.zBufferPtr, spritePool.ptr) } function pushSprite(wasmClient: WasmClient, spritePool: SpritePool, image: WasmImage, position: Vector2, z: number, scale: number, cropPosition?: Vector2, cropSize?: Vector2) { @@ -427,7 +414,7 @@ async function instantiateWasmClient(url: string): Promise { render_minimap: wasm.instance.exports.render_minimap as (display: number, display_width: number, display_height: number, camera_position_x: number, camera_position_y: number, camera_direction: number, player_position_x: number, player_position_y: number, scene: number, scene_width: number, scene_height: number, sprite_pool: number) => void, cull_and_sort_sprites: wasm.instance.exports.cull_and_sort_sprites as (camera_position_x: number, camera_position_y: number, camera_direction: number, sprite_pool: number) => void, push_sprite: wasm.instance.exports.push_sprite as (sprite_pool: number, image_pixels: number, image_width: number, image_height: number, x: number, y: number, z: number, scale: number, crop_position_x: number, crop_position_y: number, crop_size_x: number, crop_size_y: number) => void, - render_sprites: wasm.instance.exports.render_sprites as (display: number, display_width: number, display_height: number, sprite_pool: number) => void, + render_sprites: wasm.instance.exports.render_sprites as (display: number, display_width: number, display_height: number, zbuffer: number, sprite_pool: number) => void, }; } diff --git a/client.wasm b/client.wasm index c06a230bc52051ca6ce8dbd85072c63c17e0a925..3652b3d1a830340be840affc7e177305191af01d 100755 GIT binary patch delta 2321 zcma)8O=uit82-L*c4udIc6MiXHpymx+IPMdjkT2$(ru_{G6tcB(xQ0N(3HBMleD%? z1P{hptx}6r$nFFy^ltPbWG~{S1+Nvnh=>Ofyeb~O2tM!3hNM3S7l!wn_xJhUzuB8N z<@GOQL0nk7B!v+2%gw^owh={t#{W%xpZN5_3eun*1PA|E_N?>g*$jAXbdVUFz$UUhp(zvOm+LjVB30H z#V3Y8S>NQN8-o8qGuPMFh?6JNy`IQSvPLJ1j49Qd_6wv&S~gDCqeQK!=BW^fGmb!%kETQ4SrDQ( zN7lmkKf2GyB>H5gM_t0pK$oc;Q+_0seaelbE=fo$REhhWTYi-c-!tan+zZ_Bh!P|Q zT9JzJUQ6C(G|(oQ(AL(%5BKjscODQd1gBsbCq>1WOtRy?&XKjH^N1?H*$C3q%IU+)8yOS zj;R{6WiE-z5OUB-VH%v<_=GM5GE7S3Ae(AEB(DRDQQkTHJrVjIWud0CR53cm+DZ z*#uCV+FS`>=h~1#O%~J>ZjyJT7PU+B>rsPw9<^DiLG2afH(H~1c*c?BJ%uzAq;(ul zCSC{x@ZbYE8cXP+i#Rsw*~^6Ks1J^K-$pm2#6p-S!6U1S!5@O_C#V>hC~T1=M=WwH zw?NEURR_t=sv1aUR=FTXM_C|>Ar`q^W~umsIB6UQ)9$FFAcc;a0?BvOVGygM2q8FR zG@UeZZ`!v1G;`TxH5nU)gaTD2X?Aomx2r@YF=C4El*1c{h5PUl$&=baQ^_6QMmHDH zX$8syOxlB5p@3W3A<$N=OWbdF`+gZc9L*CZsLyRS7qh%hPCWQbJNr7%+4kjLttDCu z$jS)Te?-sS({ocjxSYjSAr6WV54(=G{mPzAW!HD*F}|yNdR6FAB@p~9HnoSfQ|0G$ zQ`Z(90ilEt4}@csBbOft#~~!eQ#nDHC>mRh(t!;9IvcF`lboA5DsA}kRe71 zlH?l*AWX#wh_fFEE(Iqf$qbQomjSpmj*loNx{f{N*HI!cJ*qJ?otxw$D9?V0jiG=Z zMwr)*ZOc1@->lZ)>*{JP-TU}d;{%#52r`Dhd*9{ct-+n<%ktLnujZ2(ka7CO1o``k kd}GGE)V;LUT`$>J-dTHd^yj4f(0>*8p}#7_W5Mgje}A^hYXATM delta 259 zcmZpk$=KP+$inF~zVq+8#6krgT zc;l}!FJDn=UP@|Fd~rchW=U!>10P>vPELMuVo7RzadK*2Dg*!IAB@pVCrUSmF$J+P z9blNeibI>JBW3e-j`f^eObpx*G-vWd4w=c((m5g%8X6iHq_`EB90jsefb0ea1?I_p zVmmh9k>1J9IAQWMn_noavj8apjhk#h>Xja+lnHl1X>kVB9eilc;zx6p09$Tq NZgFbKW_8aPW&pQRRuTXJ diff --git a/common.c3 b/common.c3 index 4e0dacd..ac41bc4 100644 --- a/common.c3 +++ b/common.c3 @@ -2,6 +2,7 @@ module common; import std::math; def Vector2 = float[<2>]; +def IVector2 = int[<2>]; def Vector3 = float[<3>]; def Vector4 = float[<4>];