From 238a3653e864375d6c4ad3b53d545121a727fecd Mon Sep 17 00:00:00 2001 From: Andreew Gregory Date: Tue, 30 Dec 2025 18:54:47 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=BF=D1=8F=D1=82=D1=8C=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0=D0=B6=D0=B0=D0=BB=20=D1=81=20=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D1=82=D1=83=D1=80=D0=B0=D0=BC=D0=B8.=20=D0=98=20=D0=B0?= =?UTF-8?q?=D0=BB=D0=BB=D0=BE=D0=BA=D0=B0=D1=82=D0=BE=D1=80=20=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BE?= =?UTF-8?q?=D0=BF=D1=8F=D1=82=D1=8C=20=D0=BF=D0=BE=D0=BB=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D0=BB=D1=81=D1=8F.=20=D0=98=D0=B7=20=D1=85=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D1=88=D0=B5=D0=B3=D0=BE:=20=D0=B2=20r4=20=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=85=D0=BE=D0=B4=D0=B8=D1=82=D1=8C=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D0=BB=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/l2/allie/Allie.hs | 31 +++--- src/l2/allie/Geom.hs | 30 +++++- src/l2/allie/allie.c | 102 +----------------- src/l2/anne/r4.h | 20 ++-- src/l2/margaret/vulkan_buffer_claire.h | 16 +-- src/l2/margaret/vulkan_images_claire.h | 6 +- src/l3/r4/R4.hs | 85 +++++++++++---- .../{asphalt.png => asphalt_diffuse.png} | Bin src/l3/textures/puck_diffuse.png | Bin 11286 -> 7073 bytes src/l3/textures/puck_specular.png | Bin 8740 -> 2769 bytes src/l3/textures/stick_diffuse.png | Bin 0 -> 544 bytes src/l3/textures/stick_specular.png | Bin 0 -> 282 bytes src/l_adele/alice/0sh/0sh.frag | 2 +- 13 files changed, 141 insertions(+), 151 deletions(-) rename src/l3/textures/{asphalt.png => asphalt_diffuse.png} (100%) create mode 100644 src/l3/textures/stick_diffuse.png create mode 100644 src/l3/textures/stick_specular.png diff --git a/src/l2/allie/Allie.hs b/src/l2/allie/Allie.hs index ba05356..2db5d8b 100644 --- a/src/l2/allie/Allie.hs +++ b/src/l2/allie/Allie.hs @@ -11,6 +11,7 @@ Callbacks(..), aliceMainloop, newAlice, aliceSetSkyColor, aliceNewLucyFace, aliceLucyFaceOfSize, lucyFaceAddGlyphs, aliceClearText, aliceAddText, aliceAddGenericMeshHand, aliceGenericMeshResizeInstanceArr, aliceGenericMeshSetInst, aliceAddShinyMeshHand, aliceShinyMeshResizeInstanceArr, aliceShinyMeshSetInst, aliceGetCamBack, +aliceGetCamRight, aliceGetCamUp, aliceSetFOV, aliceGetCamPos, aliceSetCamPos, aliceSetPointLightCount, aliceSetPointLight, aliceIsPressed ) where @@ -22,7 +23,6 @@ import Data.Int (Int8, Int16, Int32, Int64) import Foreign.Ptr (Ptr, FunPtr, nullPtr, plusPtr, castPtr) import Foreign.Marshal.Alloc (alloca) import Foreign.Storable (Storable(..)) -import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef) import qualified Data.Text import Data.Text.Encoding (encodeUtf8) import Data.ByteString (useAsCStringLen) @@ -97,17 +97,6 @@ aliceMainloop alice cb = alloca $ \ptr -> do poke ptr cb allieAliceMainloop alice ptr ---aliceClearScreenTextLabel :: AliceAnotherFrameCap s -> IO () ---aliceClearScreenTextLabel (AliceAnotherFrameCap alice) = allieAliceClearScreenText alice - ---aliceAddScreenTextLabel :: AliceAnotherFrameCap s -> String -> IO () ---aliceAddScreenTextLabel (AliceAnotherFrameCap alice) str = useAsCStringLen --- (encodeUtf8 $ Data.Text.pack $ str) $ \(cstr, len) -> --- allieAliceAddScreenTextLabel alice (castPtr cstr) (fromIntegral len) - - ---allieRunAlice :: Callbacks -> - useAsUtf8StringLen :: String -> (Ptr Word8 -> Word64 -> IO a) -> IO a useAsUtf8StringLen str cb = useAsCStringLen (encodeUtf8 $ Data.Text.pack $ str) $ \(cstr, len) -> cb (castPtr cstr) (fromIntegral len) @@ -180,7 +169,20 @@ aliceGetCamBack alice = alloca $ \ptr -> do allie_alice_get_cam_back alice ptr peek ptr --- todo: add right and up +foreign import ccall "allie_alice_get_cam_right" allie_alice_get_cam_right :: Alice -> Ptr Vec3 -> IO () + +aliceGetCamRight :: Alice -> IO Vec3 +aliceGetCamRight alice = alloca $ \ptr -> do + allie_alice_get_cam_right alice ptr + peek ptr + +foreign import ccall "allie_alice_get_cam_up" allie_alice_get_cam_up :: Alice -> Ptr Vec3 -> IO () + +aliceGetCamUp :: Alice -> IO Vec3 +aliceGetCamUp alice = alloca $ \ptr -> do + allie_alice_get_cam_up alice ptr + peek ptr + foreign import ccall "allie_alice_get_cam_pos" allie_alice_get_cam_pos :: Alice -> Ptr Vec3 -> IO () @@ -194,6 +196,9 @@ foreign import ccall "allie_alice_set_cam_pos" allie_alice_set_cam_pos :: Alice aliceSetCamPos :: Alice -> Vec3 -> IO () aliceSetCamPos alice (Vec3 x y z) = allie_alice_set_cam_pos alice x y z +-- Easy mapping +foreign import ccall "allie_alice_set_fov" aliceSetFOV :: Alice -> Float -> IO () + -- Maps well foreign import ccall "allie_alice_set_point_light_count" aliceSetPointLightCount :: Alice -> Int -> IO () diff --git a/src/l2/allie/Geom.hs b/src/l2/allie/Geom.hs index 42c80e6..60e097b 100644 --- a/src/l2/allie/Geom.hs +++ b/src/l2/allie/Geom.hs @@ -1,4 +1,5 @@ -module Geom(Vec2(..), Vec3(..), Vec4(..), Mat4(..), Addable(..), Multipliable(..), mat4Transit) where +module Geom(Vec2(..), Vec3(..), Vec4(..), Mat4(..), Addable(..), Multipliable(..), mat4Transit, mat4rot3d, + HasLength, normalize) where import Foreign.Storable(Storable(..)) import Foreign.Ptr (Ptr, castPtr, plusPtr) @@ -43,10 +44,24 @@ instance Multipliable Vec4 Float Vec4 where data Mat4 = Mat4 !Vec4 !Vec4 !Vec4 !Vec4 +instance Multipliable Mat4 Vec4 Vec4 where + (Mat4 vx vy vz vw) ^*^ (Vec4 ax ay az aw) = (vx ^*^ ax ^+^ vy ^*^ ay ^+^ vz ^*^ az ^+^ vw ^*^ aw) + +instance Multipliable Mat4 Mat4 Mat4 where + m ^*^ (Mat4 bx by bz bw) = Mat4 (m ^*^ bx) (m ^*^ by) (m ^*^ bz) (m ^*^ bw) + mat4Transit :: Vec3 -> Mat4 mat4Transit (Vec3 x y z) = Mat4 (Vec4 1 0 0 0) (Vec4 0 1 0 0) (Vec4 0 0 1 0) (Vec4 x y z 1) - +mat4rot3d :: Vec3 -> Float -> Mat4 +mat4rot3d (Vec3 rx ry rz) a = let cosa = cos(a) in let sina = sin(a) in + Mat4 + (Vec4 (rx * rx * (1 - cosa) + cosa) (rx * ry * (1 - cosa) + sina * rz) (rx * rz * (1 - cosa) - sina * ry) 0) + (Vec4 (rx * ry * (1 - cosa) - sina * rz) (ry * ry * (1 - cosa) + cosa) (ry * rz * (1 - cosa) + sina * rx) 0) + (Vec4 (rx * rz * (1 - cosa) + sina * ry) (ry * rz * (1 - cosa) - sina * rx) (rz * rz * (1 - cosa) + cosa) 0) + + (Vec4 0 0 0 1) + instance Storable Vec2 where sizeOf _ = 2 * sizeOf (undefined :: Float) @@ -112,4 +127,13 @@ instance Storable Mat4 where poke (castPtr ptr) v1 poke (ptr `plusPtr` vec4Size) v2 poke (ptr `plusPtr` (2 * vec4Size)) v3 - poke (ptr `plusPtr` (3 * vec4Size)) v4 \ No newline at end of file + poke (ptr `plusPtr` (3 * vec4Size)) v4 + +class HasLength a where + vlength :: a -> Float + +normalize :: (Multipliable a Float a, HasLength a) => a -> a +normalize v = v ^*^ (1 / (vlength v)) + +instance HasLength Vec2 where + vlength (Vec2 a b) = sqrt (a * a + b * b) \ No newline at end of file diff --git a/src/l2/allie/allie.c b/src/l2/allie/allie.c index 717c19b..980d24d 100644 --- a/src/l2/allie/allie.c +++ b/src/l2/allie/allie.c @@ -1395,83 +1395,6 @@ void recreate_swapchain(Alice *alice) { alice->swfb = new_swfb; } -// todo: delete it -/* It is just a stupid example */ -void update_state(Alice* alice) { - float fl = AliceWaylandApp_get_elapsed_time(&alice->wl); - // todo: ok, maybe I don't want an example. I am good enough - // if (alice->wl.first_0x80_keys[XKB_KEY_w]) - // CamControlInfo_forward(&alice->scene.cam, fl); - // if (alice->wl.first_0x80_keys[XKB_KEY_s]) - // CamControlInfo_backward(&alice->scene.cam, fl); - // if (alice->wl.first_0x80_keys[XKB_KEY_a]) - // CamControlInfo_left(&alice->scene.cam, fl); - // if (alice->wl.first_0x80_keys[XKB_KEY_d]) - // CamControlInfo_right(&alice->scene.cam, fl); - // if (alice->wl.first_0x80_keys[XKB_KEY_q]) - // CamControlInfo_down(&alice->scene.cam, fl); - // if (alice->wl.first_0x80_keys[XKB_KEY_e]) - // CamControlInfo_up(&alice->scene.cam, fl); - // - // if (alice->wl.first_0x80_keys[XKB_KEY_bracketright]) { - // for (size_t i = 0; i < alice->scene.smeshnyavka_3.len; i++) { - // ObjectInfo* oi = &alice->scene.smeshnyavka_3.buf[i]; - // vec3 p1 = alice->scene.cam.pos; - // vec3 r = vec3_normalize(vec3_minus_vec3(p1, oi->pos)); - // oi->rotation = mat3_mul_mat3(marie_3d_rot_mat3(r, fl * 0.7f), oi->rotation); - // Scene_update_smeshnyavka_3(&alice->scene, i); - // } - // } - // if (alice->wl.first_0x80_keys[XKB_KEY_bracketleft]) { - // for (size_t i = 0; i < alice->scene.smeshnyavka_1.len; i++) { - // ObjectInfo* oi = &alice->scene.smeshnyavka_1.buf[i]; - // oi->rotation = mat3_mul_mat3(marie_3d_rot_mat3((vec3){0, 0, 1}, fl * 0.4f), oi->rotation); - // Scene_update_smeshnyavka_1(&alice->scene, i); - // } - // } - // if (alice->wl.first_0x80_keys[XKB_KEY_minus]) { - // for (size_t i = 0; i < alice->scene.smeshnyavka_3.len; i++) { - // ObjectInfo* oi = &alice->scene.smeshnyavka_3.buf[i]; - // vec3 p1 = alice->scene.cam.pos; - // float dist = vec3_length(vec3_minus_vec3(p1, oi->pos)); - // float fac = 80/dist; - // oi->scale *= (1 - 0.01f * fl * fac); - // Scene_update_smeshnyavka_3(&alice->scene, i); - // } - // } - // if (alice->wl.first_0x80_keys[XKB_KEY_equal]) { - // for (size_t i = 0; i < alice->scene.smeshnyavka_3.len; i++) { - // ObjectInfo* oi = &alice->scene.smeshnyavka_3.buf[i]; - // vec3 p1 = alice->scene.cam.pos; - // float dist = vec3_length(vec3_minus_vec3(p1, oi->pos)); - // float fac = 80/dist; - // oi->scale *= (1 + 0.01f * fl * fac); - // Scene_update_smeshnyavka_3(&alice->scene, i); - // } - // } - // - // { - // GenericModelOnSceneMem* model = VecGenericModelOnSceneMem_mat(&alice->scene.generic_models, 0); - // assert(model->instance_attr.count >= 1); - // if (alice->wl.first_0x80_keys[XKB_KEY_j]) { - // alice->scene.smeshnyavka_1.buf[0].pos.x -= fl; - // Scene_update_smeshnyavka_1(&alice->scene, 0); - // } - // if (alice->wl.first_0x80_keys[XKB_KEY_k]) { - // alice->scene.smeshnyavka_1.buf[0].pos.z -= fl; - // Scene_update_smeshnyavka_1(&alice->scene, 0); - // } - // if (alice->wl.first_0x80_keys[XKB_KEY_l]) { - // alice->scene.smeshnyavka_1.buf[0].pos.z += fl; - // Scene_update_smeshnyavka_1(&alice->scene, 0); - // } - // if (alice->wl.first_0x80_keys[XKB_KEY_semicolon]) { - // alice->scene.smeshnyavka_1.buf[0].pos.x += fl; - // Scene_update_smeshnyavka_1(&alice->scene, 0); - // } - // } -} - /* It creates image views, descriptor sets, framebuffers. But not for generic models. * If we ever gonna do defragmentation, this step would have to be repeated */ void alice_create_mem_dependant_vk_obj(Alice* alice){ @@ -2046,7 +1969,8 @@ Alice* Alice_new(){ alice->dev_local_images = MargaretImgAllocator_new(alice->device, alice->physical_device, mem_type_id_device_local, 16); - + // todo: aAAAAND. We have some bug related to allocating stuff on block. This is very bad + // todo: hope will fix this night. Because this is VERY BAD alice->jane = Jane_alice_create(alice->device); /* Luckily, swapchain image allocation is not managed by me */ @@ -2236,26 +2160,6 @@ void allie_alice_shiny_mesh_resize_instance_arr(Alice* alice, ListNodeAliceShiny AliceShinyMeshHand_resize_instance_arr(alice, &mesh_hand->el, new_count); } -// void allie_alice_generic_mesh_set_inst( -// ListNodeAliceGenericMeshHand* mesh_hand, U64 index, -// float xx, float xy, float xz, float xw, float yx, float yy, float yz, float yw, -// float zx, float zy, float zz, float zw, float wx, float wy, float wz, float ww){ -// AliceGenericMeshHand_set_inst(&mesh_hand->el, index, (GenericMeshInstanceInc){.model_t = { -// .x = {xx, xy, xz, xw}, .y = {yx, yy, yz, yw}, .z = {zx, zy, zz, zw}, .w = {wx, wy, wz, ww}, -// }}); -// } -// -// void allie_alice_shiny_mesh_set_inst( -// ListNodeAliceGenericMeshHand* mesh_hand, U64 index, -// float xx, float xy, float xz, float xw, float yx, float yy, float yz, float yw, -// float zx, float zy, float zz, float zw, float wx, float wy, float wz, float ww, -// float clr_off_x, float clr_off_y, float clr_off_z, float clr_off_w, -// float clr_on_x, float clr_on_y, float clr_on_z, float clr_on_w){ -// AliceShinyMeshHand_set_inst(&mesh_hand->el, index, (ShinyMeshInstanceInc){.model_t = { -// }, .color_on = {}}); -// -// } - void allie_alice_generic_mesh_set_inst( ListNodeAliceGenericMeshHand* mesh_hand, U64 index, const GenericMeshInstanceInc* inst){ AliceGenericMeshHand_set_inst(&mesh_hand->el, index, *inst); @@ -2287,7 +2191,7 @@ void allie_alice_set_cam_pos(Alice* alice, float x, float y, float z){ } void allie_alice_set_fov(Alice* alice, float fov){ - alice->cam_info.cam.fov = fov; + alice->cam_info.cam.fov = MIN_float(MAX_float(fov, 0.001), M_PIf - 0.01); } void allie_alice_set_point_light_count(Alice* alice, int new_count){ diff --git a/src/l2/anne/r4.h b/src/l2/anne/r4.h index 2d84bcc..e9cc2a1 100644 --- a/src/l2/anne/r4.h +++ b/src/l2/anne/r4.h @@ -685,6 +685,14 @@ MarieTriangle restore_triangle_from_mesh_topology(const VecGenericMeshVertexInc* }; } +void r4_generate_flat_normal_map(VecU8 save_path){ + TextureDataR8G8B8A8 normal = TextureDataR8G8B8A8_new(1, 1); + *TextureDataR8G8B8A8_mat(&normal, 0, 0) = compress_normal_vec_into_norm_texel((vec3){0, 1, 0}); + TextureDataR8G8B8A8_write_to_png_nofail(&normal, VecU8_to_span(&save_path)); + VecU8_drop(save_path); + TextureDataR8G8B8A8_drop(normal); +} + /* r is radius, w is length of cylinder. Will write everything into files for us */ void r4_asset_gen_generic_mesh_cylinder(float s_resol, float r, float w, U32 k, VecU8 path_to_mesh, VecU8 path_to_template_tex, VecU8 path_to_normal_tex){ @@ -791,13 +799,8 @@ void r4_asset_gen_generic_mesh_cylinder(float s_resol, float r, float w, U32 k, VecU8_drop(path_to_template_tex); TextureDataR8G8B8A8_drop(template); - /* Here I generate normal tex trivially. */ - TextureDataR8G8B8A8 normal = TextureDataR8G8B8A8_new(1, 1); - *TextureDataR8G8B8A8_mat(&normal, 0, 0) = compress_normal_vec_into_norm_texel((vec3){0, 1, 0}); /* Right now it's just a pixel... */ - TextureDataR8G8B8A8_write_to_png_nofail(&normal, VecU8_to_span(&path_to_normal_tex)); - VecU8_drop(path_to_normal_tex); - TextureDataR8G8B8A8_drop(normal); + r4_generate_flat_normal_map(path_to_normal_tex); } void r4_asset_gen_generic_mesh_quad(float width, float length, VecU8 path_to_save){ @@ -887,10 +890,11 @@ int gen_assets_for_r4() { alice_write_shiny_mesh_to_file(generate_shiny_cube(0.3f), vcstr("l2/models/cube.AliceShinyMesh")); alice_write_shiny_mesh_to_file(generate_shiny_lamp(0.3f, 0.13f, 0.19f), vcstr("l2/models/lamp.AliceShinyMesh")); r4_asset_gen_generic_mesh_quad(10, 10, vcstr("l2/models/quad.AliceGenericMesh")); - r4_asset_gen_generic_mesh_cylinder(200, 0.4f, 0.06f, 5, vcstr("l2/models/puck.AliceGenericMesh"), + r4_asset_gen_generic_mesh_cylinder(200, 0.4f, 0.17f, 30, vcstr("l2/models/puck.AliceGenericMesh"), vcstr("l2/textures/puck_TEMPLATE.png"), vcstr("l2/textures/puck_NORMAL.png")); - r4_asset_gen_generic_mesh_cylinder(80, 0.13f, 1.5f, 4, vcstr("l2/models/stick.AliceGenericMesh"), + r4_asset_gen_generic_mesh_cylinder(100, 0.04f, 1.5f, 4, vcstr("l2/models/stick.AliceGenericMesh"), vcstr("l2/textures/stick_TEMPLATE.png"), vcstr("l2/textures/stick_NORMAL.png")); + r4_generate_flat_normal_map(vcstr("l2/textures/flat_NORMAL.png")); return 0; } diff --git a/src/l2/margaret/vulkan_buffer_claire.h b/src/l2/margaret/vulkan_buffer_claire.h index b22be9e..a7231c0 100644 --- a/src/l2/margaret/vulkan_buffer_claire.h +++ b/src/l2/margaret/vulkan_buffer_claire.h @@ -241,11 +241,11 @@ void MargaretBufAllocator_free(MargaretBufAllocator* self, MargaretSubbuf alloca bool eret = BufRBTree_MapU64ToU64_erase(&allocation.block->occupants, allocation.start); assert(eret); - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); } NODISCARD MargaretSubbuf MargaretBufAllocator_alloc(MargaretBufAllocator* self, U64 req_size){ - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); req_size = margaret_bump_buffer_size_to_alignment(req_size, self->alignment_exp); VkPhysicalDeviceMaintenance3Properties maintenance3_properties = { @@ -270,11 +270,11 @@ NODISCARD MargaretSubbuf MargaretBufAllocator_alloc(MargaretBufAllocator* self, new_block->occupation_counter = req_size; bool iret = BufRBTree_MapU64ToU64_insert(&new_block->occupants, 0, req_size); assert(iret); - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); return (MargaretSubbuf){.block = &self->blocks.first->el, 0, req_size}; } MargaretBufAllocator__put_buf_to_a_gap(self, free_gap.some, req_size); - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); return (MargaretSubbuf){.block = free_gap.some.block, .start = free_gap.some.start, req_size}; } @@ -305,7 +305,7 @@ NODISCARD MargaretSubbuf MargaretBufAllocator_expand( if (allocation->start + bigger_size > right_free_space.start + right_free_space.len){ return MargaretBufAllocator_alloc(self, bigger_size); } - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); MargaretBufAllocator__erase_gap(self, allocation->block, right_free_space.start, right_free_space.len); MargaretBufAllocator__insert_gap(self, allocation->block, allocation->start + bigger_size, @@ -315,7 +315,7 @@ NODISCARD MargaretSubbuf MargaretBufAllocator_expand( U64 my_it = BufRBTree_MapU64ToU64_find(&allocation->block->occupants, allocation->start); assert(my_it > 0 && my_it <= allocation->block->occupants.el.len); allocation->block->occupants.el.buf[my_it - 1].value = bigger_size; - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); return (MargaretSubbuf){0}; } @@ -342,9 +342,9 @@ void MargaretBufAllocator_expand_or_move_old_host_visible( memcpy(MargaretSubbuf_get_mapped(&maybe_bigger), MargaretSubbuf_get_mapped(allocation), allocation->len); MargaretBufAllocator_free(self, *allocation); *allocation = maybe_bigger; - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); } - MargaretBufAllocator_debug(self); + // MargaretBufAllocator_debug(self); } /* It tries to expand buffer, but if it fails, it creates a freshly-new buffer. It diff --git a/src/l2/margaret/vulkan_images_claire.h b/src/l2/margaret/vulkan_images_claire.h index cf40f53..7217a80 100644 --- a/src/l2/margaret/vulkan_images_claire.h +++ b/src/l2/margaret/vulkan_images_claire.h @@ -333,6 +333,7 @@ void MargaretImgAllocator__insert_gap(MargaretImgAllocator* self, U64 block_id, void MargaretImgAllocator__add_block(MargaretImgAllocator* self, U64 capacity){ VkDeviceMemory memory; + printf("DEBUG MargaretImgAllocator: allocating block of size %lu\n", capacity); check(vkAllocateMemory(self->device, &(VkMemoryAllocateInfo){ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = capacity, .memoryTypeIndex = self->memory_type_id @@ -486,10 +487,13 @@ NODISCARD MargaretImgAllocation MargaretImgAllocator__alloc( MargaretMemFreeSpaceManager_search(&self->mem_free_space, alignment_exp, mem_requirements.size); if (free_gap.variant == Option_None) { + // todo: there is clearly a bug that prevents free segments from appearing. I have to find it later assert(self->blocks.len > 0); U64 pitch = self->blocks.buf[self->blocks.len - 1].capacity; // Old blocks remain intact - U64 new_capacity = MAX_U64(mem_requirements.size, MIN_U64(2 * pitch, maintenance3_properties.maxMemoryAllocationSize)); + // todo: return back when done. Doing dumb things. I have a bug that prevents things from making sense + // U64 new_capacity = MAX_U64(mem_requirements.size, MIN_U64(2 * pitch, maintenance3_properties.maxMemoryAllocationSize)); + U64 new_capacity = MAX_U64(mem_requirements.size, MIN_U64(pitch, maintenance3_properties.maxMemoryAllocationSize)); MargaretImgAllocator__add_block(self, new_capacity); U64 bid = self->blocks.len - 1; MargaretImgAllocator__insert_gap(self, bid, mem_requirements.size, new_capacity - mem_requirements.size); diff --git a/src/l3/r4/R4.hs b/src/l3/r4/R4.hs index bf54349..73aadd7 100644 --- a/src/l3/r4/R4.hs +++ b/src/l3/r4/R4.hs @@ -1,6 +1,9 @@ import Allie import Geom -import Control.Monad (forM_) +import Control.Monad (forM_, when) +import Data.Char (ord) +import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef) + goodColorOfCube :: (Integral a) => a -> Vec3 goodColorOfCube i = ((Vec3 100 0 0) ^*^ t) ^+^ ((Vec3 0 50 90) ^*^ (1 - t)) where t = ((fromIntegral i :: Float) / 4) @@ -8,10 +11,26 @@ goodColorOfCube i = ((Vec3 100 0 0) ^*^ t) ^+^ ((Vec3 0 50 90) ^*^ (1 - t)) wher goodLightPos :: (Integral a) => a -> Vec3 goodLightPos i = ((Vec3 0 2 1) ^*^ t) ^+^ ((Vec3 5 1 1) ^*^ (1 - t)) where t = ((fromIntegral i :: Float) / 4) +projectDirOntoPlane :: Vec3 -> Vec2 +projectDirOntoPlane (Vec3 x y z) = normalize (Vec2 x z) + +heroHeight :: Float +heroHeight = 1.89 + +heroCamPos :: Vec2 -> Vec3 +heroCamPos (Vec2 x z) = Vec3 x heroHeight z + +puckLevitationHeight :: Float +puckLevitationHeight = 0.4 + 0.2 + +puckSpots :: [Vec2] +puckSpots = [(Vec2 (-10) (-10)), (Vec2 (-15) (-15)) , (Vec2 (-18) (-18)), (Vec2 (-18) (-25)), (Vec2 (-18) (-30))] + main :: IO() main = do alice <- newAlice aliceSetSkyColor alice (Vec4 0.9 0 0.6 1) + aliceSetCamPos alice (Vec3 0 heroHeight 0) face <- aliceNewLucyFace alice "src/l3/fonts/DMSerifText-Regular.ttf" faceOf40 <- aliceLucyFaceOfSize face 40 lucyFaceAddGlyphs faceOf40 32 (126 - 32 + 1) @@ -20,37 +39,67 @@ main = do weirdStructure <- aliceAddGenericMeshHand alice "gen/l2/models/log_10_2_6.AliceGenericMesh" "src/l3/textures/log_10_2_6_diffuse.png" "gen/l2/textures/log_10_2_6_NORMAL.png" "src/l3/textures/log_10_2_6_specular.png" aliceGenericMeshResizeInstanceArr alice weirdStructure 1 - aliceGenericMeshSetInst weirdStructure 0 (AliceGenericMeshInstance (mat4Transit (Vec3 (-3.0) (-2.0) (-5.0)))) + + aliceGenericMeshSetInst weirdStructure 0 (AliceGenericMeshInstance (mat4Transit (Vec3 (-3.0) (0.0) (-5.0)))) + + floorTile <- aliceAddGenericMeshHand alice "gen/l2/models/quad.AliceGenericMesh" + "src/l3/textures/asphalt_diffuse.png" "gen/l2/textures/flat_NORMAL.png" "src/l3/textures/funny_floor_specular.png" + aliceGenericMeshResizeInstanceArr alice floorTile 49 + + puck <- aliceAddGenericMeshHand alice "gen/l2/models/puck.AliceGenericMesh" + "src/l3/textures/puck_diffuse.png" "gen/l2/textures/puck_NORMAL.png" "src/l3/textures/puck_specular.png" + + forM_ [0..6] $ \x -> forM_ [0..6] $ \z -> + aliceGenericMeshSetInst floorTile (z * 7 + x) (AliceGenericMeshInstance + (mat4Transit (Vec3 ((fromIntegral x) * 10 - 35) 0 ((fromIntegral z) * 10 - 35)))) cube <- aliceAddShinyMeshHand alice "gen/l2/models/cube.AliceShinyMesh" aliceShinyMeshResizeInstanceArr alice cube 5 - aliceSetPointLightCount alice 5 forM_ [0..4] $ \i -> do - aliceShinyMeshSetInst cube i (AliceShinyMeshInstance (mat4Transit (goodLightPos i)) (Vec3 1 1 1) (goodColorOfCube i)) + aliceShinyMeshSetInst cube i (AliceShinyMeshInstance (mat4Transit (goodLightPos i)) (Vec3 0.0 0.0 0.0) (goodColorOfCube i)) aliceSetPointLight alice (fromIntegral i) (AlicePointLight (goodLightPos i) (goodColorOfCube i)) - -- state <- newIORef 67 + aliceGenericMeshResizeInstanceArr alice puck (fromIntegral $ length puckSpots) + forM_ (zip[0..] puckSpots) $ \(i, (Vec2 x z)) -> aliceGenericMeshSetInst puck (fromIntegral i) (AliceGenericMeshInstance + (mat4Transit (Vec3 x puckLevitationHeight z ))) + + heroPos <- newIORef (Vec2 0 0) -- Create the Callbacks structure. let callbacks = Callbacks myonKeyboardKey myonAnotherFrame where myonKeyboardKey keysym keyAction = do - -- old <- readIORef state - -- writeIORef state (old + 1) putStrLn ("Got a keypress") myonAnotherFrame fl = do - oldPos <- aliceGetCamPos alice - goForward <- aliceIsPressed alice 0x77 - if goForward - then do - backDir <- aliceGetCamBack alice - aliceSetCamPos alice (oldPos ^+^ (backDir ^*^ (-fl * 10))) - else return () + backDir <- aliceGetCamBack alice + let projBack = projectDirOntoPlane backDir + rightDir <- aliceGetCamRight alice + let projRight = projectDirOntoPlane rightDir + goForward <- aliceIsPressed alice (fromIntegral $ ord 'w') + when goForward $ do + curHeroPos <- readIORef heroPos + let nextHeroPos = (curHeroPos ^+^ (projBack ^*^ (fl * (-10)))) + writeIORef heroPos nextHeroPos + aliceSetCamPos alice $ heroCamPos nextHeroPos + goBackward <- aliceIsPressed alice (fromIntegral $ ord 's') + when goBackward $ do + curHeroPos <- readIORef heroPos + let nextHeroPos = (curHeroPos ^+^ (projBack ^*^ (fl * (10)))) + writeIORef heroPos nextHeroPos + aliceSetCamPos alice $ heroCamPos nextHeroPos + goLeft <- aliceIsPressed alice (fromIntegral $ ord 'a') + when goLeft $ do + curHeroPos <- readIORef heroPos + let nextHeroPos = (curHeroPos ^+^ (projRight ^*^ (fl * (-10)))) + writeIORef heroPos nextHeroPos + aliceSetCamPos alice $ heroCamPos nextHeroPos + goRight <- aliceIsPressed alice (fromIntegral $ ord 'd') + when goRight $ do + curHeroPos <- readIORef heroPos + let nextHeroPos = (curHeroPos ^+^ (projRight ^*^ (fl * (10)))) + writeIORef heroPos nextHeroPos + aliceSetCamPos alice $ heroCamPos nextHeroPos - --cur <- readIORef state - --aliceClearScreenTextLabel alicePerm - --aliceAddScreenTextLabel alicePerm ("Current value is = " ++ show cur) - -- Allocate space for the struct, poke it, and pass to C. aliceMainloop alice callbacks diff --git a/src/l3/textures/asphalt.png b/src/l3/textures/asphalt_diffuse.png similarity index 100% rename from src/l3/textures/asphalt.png rename to src/l3/textures/asphalt_diffuse.png diff --git a/src/l3/textures/puck_diffuse.png b/src/l3/textures/puck_diffuse.png index e7ff42ffcabcfe503501f553e27b3e597ebbe35a..9081005702354bed7b1217bc89134acaf7994262 100644 GIT binary patch literal 7073 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 LNkvXXu0mjf)BW`T literal 11286 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 A0RR91 diff --git a/src/l3/textures/puck_specular.png b/src/l3/textures/puck_specular.png index ccfe4497b2300d76bdbd4bcf120e0f7474928869..fb0e95a06e63165836172900d983f109b6af2d8c 100644 GIT binary patch literal 2769 zcma)8`9IT-AAkEwau*dPRO_RUZfF%8_V&FjuiTB1X

-9YTfY+m&u> zXrBP5Dz3+?OEz83a20Fa;z0LiHUz}>f! z7Xctf69DL50ATn60AwSIJM4`21Ca;|mnZ-blldKONvNwtYAHarY?-Df6LTlvqomS(}q`XY`i+{Z6!7GEhM2i{W0Zd_oM zO!q3v2OL<$-qEq)D8pvGLm;j}BIS4M@-BNcM~bGpoBa6e|EK?TAJ`c;ZQFEwgBq5l z7z8k~=I5Wt6)zT@nRR9kVDhsU$kBJ)wk1n2vl>&wuyd^$N=FObXB|p6=2aM%lAj%h zi+H2G^ukJaN{e9AW++dBX2Ep$Z4~9cFk+3-r|y}cRx{=a?d^FSjc z%y9@qFd)I_F^D3?|DoNfWop}1u`@-s4w5w;?RsFW!V+iCW{!U|>tCiDIBuMtJ8C-2 zx7Z*ar@vU6UlvaA?k~#IG#kzlDD4gN#`=ldZGqnDuVZx-cREJOYL_GlhIO*+wgqr= zQ&sJ}WRLP2s4*}1p@Mbzp;|mV+)5i9wCo!utIZZmZ=hGd5QC9u3i5GJuP^~dnWYMwlps@F-ens)f<=6 zLAkwgjP1|ewO|NJ8lLOQ()SGXEL+X#gOru0v{R05{mnMu?*7dXJe%;QMt^+l`=sgC z&Y2&=j!|-)qcNos@u%Js9yw+t<+U_pT89@Qk;$$6ni4QVf@L2oA1J4F<|SwbvA{5u z*fQbB#V8?lkUAVWc-L#u4mVCrOeq(?2x`}_rQ>)GejOkj=^LVCg;+(8T|0Py1_~tJtM-CjQ(1HX?w8=Mc#sIe!SVeRs84kdi1uy=Rh8iT<;(vy-9uE6cx^e-Ph`u-hDVGbHLd=oO%aiGz_+^T zYtR`h6a3;O&JmrHCv424>9eAu)iXn{R8RZlU_0V?X0P8*y;9-AO>_7GNaMVt79Zs4J% zYzk=#iPXxkt{ru8r}{z=4Ki}lbMVdVL_7KeowLtQlUqx#oj}2C*c{Lek4tXB|H2z* zYyMU?tJ0-HzK{A7M=&t7^@cSp7PPYr!Y|nvJ_-sVF&!6vUwpUq!ttbT@fPS5KD< zKfu}C?&3>O1z^Uky!vlDoIe`-15GL!=MjsG=Y|F?U&NU~cEed%C#&jfgdKZZTPu}H z51IN4na|$!|Cl)4)iAKl>z7SZsYHDtut}r+GzT3ku>0ORZ}~e)s6A99?JpbD9s^?? z2~}lrkz;YBcY7!Ui`qBIST=Ru(N6vw*@!Ieqe|q6{ zdC!i>i5Y*xkDnYjadTLPLY`3{D-O|cLuq)r9Kg7K{~Bo0_VHGlC9A}W2H6$iD&Xba zIDRp`w%(D|v@pB^KOdxlM|VaP>26qyxIYZL^*DfF*|gf7-Q&M{g&H-+S$jpGbRrF&<25dusY!<0eC_GAfTbAJy!1@=v#w3)ZEUq`yJE^|{1-;;wOY z7+nUAe~Pe@fkHD=tmBAn&&B*Z$rq#exrYirAK)xu=e zc61G?vjNS1Yzl5@tIZ$_7D*U}Y}KVH!WG7^2P;`1(-o*-O_}_b?*1xTWRrm@%YglD zp+ZsyjBr8=`ANl!@W0W>>%aOOy0?S3lP3dfq1kjX#(Qe2LS{T!6b5k8GD|DD(nf7O z#%s)of9Kn>6i>yubu-U^jF3h2%0hw0#Mj827sQ#G?p_E!v?~Ra;aaXVJ<@OctI6Ec zzm(Sow5j=(mkcFOrxrGHFYx#@v&%r#0QO!T&a_Db33;e`RygUvP^Q5t9$mPv?P6qp z|4Ln1Un3fM_NIBv;kez!VaBhOhqgaluL}ESr1mG_er6gRHOfU)3NO7b6KoWG=S($z zpJ^bDIKtQC`QJVw>_w`ahUXkrotnZzRBw%R{Gpjf8 Gdhj3r$ew}# literal 8740 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 I!=>c^0h+W=L;wH) diff --git a/src/l3/textures/stick_diffuse.png b/src/l3/textures/stick_diffuse.png new file mode 100644 index 0000000000000000000000000000000000000000..d4168365a30271ea4c9cac28fdf034efbd2bf543 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^;y^r~gAGUuopcffQjEnx?oNz1PwLbIIV|apzK#qG z8~eHcB(ehe3dtTpz6=aiY77hwEes65fIGg6EN+E?c3;uw-~@9njNSldqlYGJosCij$CRMX)&|a`KAzMy87l6kLn-n zOt0S5C9CcB-tv3*f*Dq931AYizq3In?Q>}D-(R0Dobr`rUbo$0+OZ4w zHwhK&Wv%&M%s=mJ_=>LbL?HLSJ`uk2oE=xq7-if-t!&}!>fv%i0>y&}J*d{fS(Q!~Nn{1`6_P!Id>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC0W`2+&aOZkpaf@u zM`SSr1Gg{;GcwGYBLNg-FY)wsWq-*d$0sddw(a(2piqIQi(`n#@weyp3La44XnQC= zSuHx1&8=7^I_$`IlU|Gr5*|jcIPM;JTp&Wh)X!3`^@+I>922=O)2`dK1Of) z>8C|ILDc`BnGzODqQ3APUO3I~bi;$Sm!H>OGP-el-diPK_ZOj3JM6xiHuC395h@pB S;RpuW&EV