diff --git a/assets/chunks/-1_0_0.dcf b/assets/chunks/-1_0_0.dcf index e35ec480..640ff6e5 100644 Binary files a/assets/chunks/-1_0_0.dcf and b/assets/chunks/-1_0_0.dcf differ diff --git a/assets/chunks/0_0_0.dcf b/assets/chunks/0_0_0.dcf index e6a1256f..aa2959c7 100644 Binary files a/assets/chunks/0_0_0.dcf and b/assets/chunks/0_0_0.dcf differ diff --git a/assets/chunks/0_1_0.dcf b/assets/chunks/0_1_0.dcf index 7e18574a..7429ed6c 100644 Binary files a/assets/chunks/0_1_0.dcf and b/assets/chunks/0_1_0.dcf differ diff --git a/assets/chunks/1_0_0.dcf b/assets/chunks/1_0_0.dcf index 7c10bc21..44ace2cb 100644 Binary files a/assets/chunks/1_0_0.dcf and b/assets/chunks/1_0_0.dcf differ diff --git a/assets/chunks/1_1_0.dcf b/assets/chunks/1_1_0.dcf index 3ff4b702..fb28d758 100644 Binary files a/assets/chunks/1_1_0.dcf and b/assets/chunks/1_1_0.dcf differ diff --git a/assets/models/buildings/house_1_1.json b/assets/models/buildings/house_1_1.json new file mode 100644 index 00000000..f3231bce --- /dev/null +++ b/assets/models/buildings/house_1_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_1_2.json b/assets/models/buildings/house_1_2.json new file mode 100644 index 00000000..6962b659 --- /dev/null +++ b/assets/models/buildings/house_1_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_1_3.json b/assets/models/buildings/house_1_3.json new file mode 100644 index 00000000..3c369e8e --- /dev/null +++ b/assets/models/buildings/house_1_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_1_4.json b/assets/models/buildings/house_1_4.json new file mode 100644 index 00000000..81b57861 --- /dev/null +++ b/assets/models/buildings/house_1_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_1_5.json b/assets/models/buildings/house_1_5.json new file mode 100644 index 00000000..36d93d6c --- /dev/null +++ b/assets/models/buildings/house_1_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_1_6.json b/assets/models/buildings/house_1_6.json new file mode 100644 index 00000000..c544d562 --- /dev/null +++ b/assets/models/buildings/house_1_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_1_7.json b/assets/models/buildings/house_1_7.json new file mode 100644 index 00000000..7f419a27 --- /dev/null +++ b/assets/models/buildings/house_1_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_1_8.json b/assets/models/buildings/house_1_8.json new file mode 100644 index 00000000..b741c12f --- /dev/null +++ b/assets/models/buildings/house_1_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_1_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_1.json b/assets/models/buildings/house_2_1.json new file mode 100644 index 00000000..693ee13f --- /dev/null +++ b/assets/models/buildings/house_2_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_2.json b/assets/models/buildings/house_2_2.json new file mode 100644 index 00000000..83080cd5 --- /dev/null +++ b/assets/models/buildings/house_2_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_3.json b/assets/models/buildings/house_2_3.json new file mode 100644 index 00000000..172b0784 --- /dev/null +++ b/assets/models/buildings/house_2_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_4.json b/assets/models/buildings/house_2_4.json new file mode 100644 index 00000000..c0b4a34b --- /dev/null +++ b/assets/models/buildings/house_2_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_5.json b/assets/models/buildings/house_2_5.json new file mode 100644 index 00000000..a450c254 --- /dev/null +++ b/assets/models/buildings/house_2_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_6.json b/assets/models/buildings/house_2_6.json new file mode 100644 index 00000000..6b9e98ab --- /dev/null +++ b/assets/models/buildings/house_2_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_7.json b/assets/models/buildings/house_2_7.json new file mode 100644 index 00000000..b93f7400 --- /dev/null +++ b/assets/models/buildings/house_2_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_2_8.json b/assets/models/buildings/house_2_8.json new file mode 100644 index 00000000..a306084d --- /dev/null +++ b/assets/models/buildings/house_2_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_2_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_1.json b/assets/models/buildings/house_3_1.json new file mode 100644 index 00000000..8a51936f --- /dev/null +++ b/assets/models/buildings/house_3_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_2.json b/assets/models/buildings/house_3_2.json new file mode 100644 index 00000000..91a189a9 --- /dev/null +++ b/assets/models/buildings/house_3_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_3.json b/assets/models/buildings/house_3_3.json new file mode 100644 index 00000000..98f13cca --- /dev/null +++ b/assets/models/buildings/house_3_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_4.json b/assets/models/buildings/house_3_4.json new file mode 100644 index 00000000..f9413bde --- /dev/null +++ b/assets/models/buildings/house_3_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_5.json b/assets/models/buildings/house_3_5.json new file mode 100644 index 00000000..71507711 --- /dev/null +++ b/assets/models/buildings/house_3_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_6.json b/assets/models/buildings/house_3_6.json new file mode 100644 index 00000000..5eb7865d --- /dev/null +++ b/assets/models/buildings/house_3_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_7.json b/assets/models/buildings/house_3_7.json new file mode 100644 index 00000000..7bcc8b5a --- /dev/null +++ b/assets/models/buildings/house_3_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_3_8.json b/assets/models/buildings/house_3_8.json new file mode 100644 index 00000000..0643ca47 --- /dev/null +++ b/assets/models/buildings/house_3_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_3_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_1.json b/assets/models/buildings/house_4_1.json new file mode 100644 index 00000000..73a911cd --- /dev/null +++ b/assets/models/buildings/house_4_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_2.json b/assets/models/buildings/house_4_2.json new file mode 100644 index 00000000..db055224 --- /dev/null +++ b/assets/models/buildings/house_4_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_3.json b/assets/models/buildings/house_4_3.json new file mode 100644 index 00000000..64e39039 --- /dev/null +++ b/assets/models/buildings/house_4_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_4.json b/assets/models/buildings/house_4_4.json new file mode 100644 index 00000000..8963ba12 --- /dev/null +++ b/assets/models/buildings/house_4_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_5.json b/assets/models/buildings/house_4_5.json new file mode 100644 index 00000000..c13ba9e3 --- /dev/null +++ b/assets/models/buildings/house_4_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_6.json b/assets/models/buildings/house_4_6.json new file mode 100644 index 00000000..9fc76393 --- /dev/null +++ b/assets/models/buildings/house_4_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_7.json b/assets/models/buildings/house_4_7.json new file mode 100644 index 00000000..c7545cac --- /dev/null +++ b/assets/models/buildings/house_4_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_4_8.json b/assets/models/buildings/house_4_8.json new file mode 100644 index 00000000..a470127f --- /dev/null +++ b/assets/models/buildings/house_4_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_4_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_1.json b/assets/models/buildings/house_5_1.json new file mode 100644 index 00000000..7966a3e8 --- /dev/null +++ b/assets/models/buildings/house_5_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_2.json b/assets/models/buildings/house_5_2.json new file mode 100644 index 00000000..cdbc0a7c --- /dev/null +++ b/assets/models/buildings/house_5_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_3.json b/assets/models/buildings/house_5_3.json new file mode 100644 index 00000000..657900a1 --- /dev/null +++ b/assets/models/buildings/house_5_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_4.json b/assets/models/buildings/house_5_4.json new file mode 100644 index 00000000..ce52eac1 --- /dev/null +++ b/assets/models/buildings/house_5_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_5.json b/assets/models/buildings/house_5_5.json new file mode 100644 index 00000000..0223c690 --- /dev/null +++ b/assets/models/buildings/house_5_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_6.json b/assets/models/buildings/house_5_6.json new file mode 100644 index 00000000..769373be --- /dev/null +++ b/assets/models/buildings/house_5_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_7.json b/assets/models/buildings/house_5_7.json new file mode 100644 index 00000000..c428dbc2 --- /dev/null +++ b/assets/models/buildings/house_5_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_5_8.json b/assets/models/buildings/house_5_8.json new file mode 100644 index 00000000..2efa8d5b --- /dev/null +++ b/assets/models/buildings/house_5_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_5_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_1.json b/assets/models/buildings/house_6_1.json new file mode 100644 index 00000000..d2a4d5be --- /dev/null +++ b/assets/models/buildings/house_6_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_2.json b/assets/models/buildings/house_6_2.json new file mode 100644 index 00000000..c3f0a02f --- /dev/null +++ b/assets/models/buildings/house_6_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_3.json b/assets/models/buildings/house_6_3.json new file mode 100644 index 00000000..f145bf86 --- /dev/null +++ b/assets/models/buildings/house_6_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_4.json b/assets/models/buildings/house_6_4.json new file mode 100644 index 00000000..6c37797c --- /dev/null +++ b/assets/models/buildings/house_6_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_5.json b/assets/models/buildings/house_6_5.json new file mode 100644 index 00000000..b2d252ce --- /dev/null +++ b/assets/models/buildings/house_6_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_6.json b/assets/models/buildings/house_6_6.json new file mode 100644 index 00000000..e50bba0c --- /dev/null +++ b/assets/models/buildings/house_6_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_7.json b/assets/models/buildings/house_6_7.json new file mode 100644 index 00000000..21ff16fd --- /dev/null +++ b/assets/models/buildings/house_6_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_6_8.json b/assets/models/buildings/house_6_8.json new file mode 100644 index 00000000..a2c66829 --- /dev/null +++ b/assets/models/buildings/house_6_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_6_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_1.json b/assets/models/buildings/house_7_1.json new file mode 100644 index 00000000..0017a2f4 --- /dev/null +++ b/assets/models/buildings/house_7_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_2.json b/assets/models/buildings/house_7_2.json new file mode 100644 index 00000000..d62cfc35 --- /dev/null +++ b/assets/models/buildings/house_7_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_3.json b/assets/models/buildings/house_7_3.json new file mode 100644 index 00000000..112826b2 --- /dev/null +++ b/assets/models/buildings/house_7_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_4.json b/assets/models/buildings/house_7_4.json new file mode 100644 index 00000000..3a68ee2e --- /dev/null +++ b/assets/models/buildings/house_7_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_5.json b/assets/models/buildings/house_7_5.json new file mode 100644 index 00000000..11a4aba9 --- /dev/null +++ b/assets/models/buildings/house_7_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_6.json b/assets/models/buildings/house_7_6.json new file mode 100644 index 00000000..ab1f64e2 --- /dev/null +++ b/assets/models/buildings/house_7_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_7.json b/assets/models/buildings/house_7_7.json new file mode 100644 index 00000000..22262621 --- /dev/null +++ b/assets/models/buildings/house_7_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_7_8.json b/assets/models/buildings/house_7_8.json new file mode 100644 index 00000000..8797c640 --- /dev/null +++ b/assets/models/buildings/house_7_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_7_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_1.json b/assets/models/buildings/house_8_1.json new file mode 100644 index 00000000..dc1860fa --- /dev/null +++ b/assets/models/buildings/house_8_1.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_1.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_2.json b/assets/models/buildings/house_8_2.json new file mode 100644 index 00000000..4a32f105 --- /dev/null +++ b/assets/models/buildings/house_8_2.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_2.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_3.json b/assets/models/buildings/house_8_3.json new file mode 100644 index 00000000..2b7c73b7 --- /dev/null +++ b/assets/models/buildings/house_8_3.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_3.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_4.json b/assets/models/buildings/house_8_4.json new file mode 100644 index 00000000..29a2a49c --- /dev/null +++ b/assets/models/buildings/house_8_4.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_4.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_5.json b/assets/models/buildings/house_8_5.json new file mode 100644 index 00000000..54788a80 --- /dev/null +++ b/assets/models/buildings/house_8_5.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_5.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_6.json b/assets/models/buildings/house_8_6.json new file mode 100644 index 00000000..6d2e35b6 --- /dev/null +++ b/assets/models/buildings/house_8_6.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_6.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_7.json b/assets/models/buildings/house_8_7.json new file mode 100644 index 00000000..41009c69 --- /dev/null +++ b/assets/models/buildings/house_8_7.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_7.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/buildings/house_8_8.json b/assets/models/buildings/house_8_8.json new file mode 100644 index 00000000..800d2758 --- /dev/null +++ b/assets/models/buildings/house_8_8.json @@ -0,0 +1,4 @@ +{ + "mesh": "meshes/buildings/house_8_8.dmf", + "color": [255, 0, 255, 255] +} diff --git a/assets/models/chunks/chunk_-1_0_0_0.json b/assets/models/chunks/chunk_-1_0_0_0.json new file mode 100644 index 00000000..e2757646 --- /dev/null +++ b/assets/models/chunks/chunk_-1_0_0_0.json @@ -0,0 +1,10 @@ +{ + "mesh": "meshes/chunks/chunk_-1_0_0_0.dmf", + "color": [ + 255, + 255, + 255, + 255 + ], + "texture": "tiles.png" +} \ No newline at end of file diff --git a/assets/models/chunks/chunk_0_0_0_0.json b/assets/models/chunks/chunk_0_0_0_0.json new file mode 100644 index 00000000..7715800a --- /dev/null +++ b/assets/models/chunks/chunk_0_0_0_0.json @@ -0,0 +1,10 @@ +{ + "mesh": "meshes/chunks/chunk_0_0_0_0.dmf", + "color": [ + 255, + 255, + 255, + 255 + ], + "texture": "tiles.png" +} \ No newline at end of file diff --git a/assets/models/chunks/chunk_0_1_0_0.json b/assets/models/chunks/chunk_0_1_0_0.json new file mode 100644 index 00000000..e2da8643 --- /dev/null +++ b/assets/models/chunks/chunk_0_1_0_0.json @@ -0,0 +1,10 @@ +{ + "mesh": "meshes/chunks/chunk_0_1_0_0.dmf", + "color": [ + 255, + 255, + 255, + 255 + ], + "texture": "tiles.png" +} \ No newline at end of file diff --git a/assets/models/chunks/chunk_1_0_0_0.json b/assets/models/chunks/chunk_1_0_0_0.json new file mode 100644 index 00000000..1c5947f8 --- /dev/null +++ b/assets/models/chunks/chunk_1_0_0_0.json @@ -0,0 +1,10 @@ +{ + "mesh": "meshes/chunks/chunk_1_0_0_0.dmf", + "color": [ + 255, + 255, + 255, + 255 + ], + "texture": "tiles.png" +} \ No newline at end of file diff --git a/assets/models/chunks/chunk_1_1_0_0.json b/assets/models/chunks/chunk_1_1_0_0.json new file mode 100644 index 00000000..b68b098c --- /dev/null +++ b/assets/models/chunks/chunk_1_1_0_0.json @@ -0,0 +1,10 @@ +{ + "mesh": "meshes/chunks/chunk_1_1_0_0.dmf", + "color": [ + 255, + 255, + 255, + 255 + ], + "texture": "tiles.png" +} \ No newline at end of file diff --git a/assets/tiles.png b/assets/tiles.png new file mode 100644 index 00000000..bc170af1 Binary files /dev/null and b/assets/tiles.png differ diff --git a/editor/client/public/index.html b/editor/client/public/index.html new file mode 100644 index 00000000..3507e45b --- /dev/null +++ b/editor/client/public/index.html @@ -0,0 +1 @@ +

Hello Homepage

diff --git a/editor/client/public/template.html b/editor/client/public/template.html new file mode 100644 index 00000000..976e6fe4 --- /dev/null +++ b/editor/client/public/template.html @@ -0,0 +1,11 @@ + + + + + + Dusk Map Editor + + +
+ + diff --git a/editor/server/server.py b/editor/server/server.py new file mode 100644 index 00000000..07fe3052 --- /dev/null +++ b/editor/server/server.py @@ -0,0 +1,224 @@ +""" +Map Editor HTTP Server + +Serves editor/client/public/ as static files with smart routing: + / -> index.html + /page -> page.html or page/index.html + /index -> index.html + +API endpoints (all paths relative to project root): + GET /api/ls?path= directory listing + GET /api/read?path= file contents + POST /api/write?path= write file (JSON body: {"content":"..."}) + POST /api/mkdir?path= create directory +""" + +import os +import json +import mimetypes +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import urlparse, parse_qs, unquote +from pathlib import Path + +PORT = 3000 +SCRIPT_DIR = Path(__file__).parent.resolve() +PUBLIC_DIR = SCRIPT_DIR.parent / "client" / "public" +PROJECT_ROOT = SCRIPT_DIR.parent.parent.resolve() + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + parsed = urlparse(self.path) + path = unquote(parsed.path) + if path.startswith("/api/"): + params = parse_qs(parsed.query) + if path == "/api/ls": + self.api_ls(params) + elif path == "/api/read": + self.api_read(params) + else: + self.send_error(404) + else: + self.serve_static(path) + + def do_POST(self): + parsed = urlparse(self.path) + params = parse_qs(parsed.query) + if parsed.path == "/api/write": + self.api_write(params) + elif parsed.path == "/api/mkdir": + self.api_mkdir(params) + else: + self.send_error(404) + + # ------------------------------------------------------------------ + # Helpers + # ------------------------------------------------------------------ + + def resolve_project_path(self, rel): + rel = rel.lstrip("/") + target = (PROJECT_ROOT / rel).resolve() + if not str(target).startswith(str(PROJECT_ROOT)): + return None + return target + + def send_json(self, data, code=200): + body = json.dumps(data, indent=2).encode("utf-8") + self.send_response(code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", len(body)) + self.end_headers() + self.wfile.write(body) + + def read_body(self): + length = int(self.headers.get("Content-Length", 0)) + return self.rfile.read(length) + + # ------------------------------------------------------------------ + # API handlers + # ------------------------------------------------------------------ + + def api_ls(self, params): + rel = params.get("path", [""])[0] + target = self.resolve_project_path(rel) + if target is None: + self.send_json({"error": "invalid path"}, 400) + return + if not target.exists(): + self.send_json({"error": "path not found"}, 404) + return + if not target.is_dir(): + self.send_json({"error": "not a directory"}, 400) + return + + entries = [] + for entry in sorted(target.iterdir(), key=lambda e: (e.is_file(), e.name)): + entries.append({ + "name": entry.name, + "type": "dir" if entry.is_dir() else "file", + "size": entry.stat().st_size if entry.is_file() else None, + }) + self.send_json({ + "path": str(target.relative_to(PROJECT_ROOT)), + "entries": entries, + }) + + def api_read(self, params): + rel = params.get("path", [""])[0] + target = self.resolve_project_path(rel) + if target is None: + self.send_json({"error": "invalid path"}, 400) + return + if not target.exists(): + self.send_json({"error": "file not found"}, 404) + return + if not target.is_file(): + self.send_json({"error": "not a file"}, 400) + return + + try: + content = target.read_text(encoding="utf-8") + self.send_json({ + "path": str(target.relative_to(PROJECT_ROOT)), + "content": content, + }) + except UnicodeDecodeError: + import base64 + content = base64.b64encode(target.read_bytes()).decode("ascii") + self.send_json({ + "path": str(target.relative_to(PROJECT_ROOT)), + "content": content, + "encoding": "base64", + }) + + def api_write(self, params): + rel = params.get("path", [""])[0] + target = self.resolve_project_path(rel) + if target is None: + self.send_json({"error": "invalid path"}, 400) + return + + body = self.read_body() + try: + data = json.loads(body) + content = data.get("content", "") + except (json.JSONDecodeError, UnicodeDecodeError): + content = body.decode("utf-8") + + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content, encoding="utf-8") + self.send_json({"ok": True, "path": str(target.relative_to(PROJECT_ROOT))}) + + def api_mkdir(self, params): + rel = params.get("path", [""])[0] + target = self.resolve_project_path(rel) + if target is None: + self.send_json({"error": "invalid path"}, 400) + return + target.mkdir(parents=True, exist_ok=True) + self.send_json({"ok": True, "path": str(target.relative_to(PROJECT_ROOT))}) + + # ------------------------------------------------------------------ + # Static file serving + # ------------------------------------------------------------------ + + def serve_static(self, path): + clean = path.lstrip("/") + candidates = [] + + if clean == "" or clean == "index" or clean == "index.html": + candidates = ["index.html"] + else: + candidates.append(clean) + if "." not in Path(clean).name: + candidates.append(clean + ".html") + candidates.append(clean + "/index.html") + + for candidate in candidates: + file_path = PUBLIC_DIR / candidate + if file_path.is_file(): + self.serve_file(file_path) + return + + self.send_error(404) + + def serve_file(self, file_path): + mime, _ = mimetypes.guess_type(str(file_path)) + mime = mime or "application/octet-stream" + + template = PUBLIC_DIR / "template.html" + if ( + mime == "text/html" + and file_path.name != "template.html" + and template.is_file() + ): + fragment = file_path.read_text(encoding="utf-8") + shell = template.read_text(encoding="utf-8") + content = shell.replace( + '
', + f'
{fragment}
', + 1, + ).encode("utf-8") + else: + content = file_path.read_bytes() + + self.send_response(200) + self.send_header("Content-Type", mime) + self.send_header("Content-Length", len(content)) + self.end_headers() + self.wfile.write(content) + + def log_message(self, fmt, *args): + print(f" {self.address_string()} {fmt % args}") + + +if __name__ == "__main__": + print(f"Map Editor") + print(f" http://localhost:{PORT}") + print(f" public : {PUBLIC_DIR}") + print(f" project: {PROJECT_ROOT}") + server = HTTPServer(("", PORT), Handler) + try: + server.serve_forever() + except KeyboardInterrupt: + print("\nStopped.") diff --git a/editor/start.sh b/editor/start.sh new file mode 100755 index 00000000..e1871d04 --- /dev/null +++ b/editor/start.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e +cd "$(dirname "$0")" +python3 server/server.py diff --git a/src/dusk/asset/asset.c b/src/dusk/asset/asset.c index 55cf4933..6ac7a4e9 100644 --- a/src/dusk/asset/asset.c +++ b/src/dusk/asset/asset.c @@ -411,16 +411,26 @@ errorret_t assetDispose(void) { assertIsMainThread("Must be called from the main thread."); threadStop(&ASSET.loadThread); - // Dispose every non-null entry so type-specific dispose callbacks - // (e.g. assetScriptDispose freeing jerry values) run before the - // scripting engine is torn down. - assetentry_t *entry = ASSET.entries; + // Drain-dispose: repeatedly find and dispose zero-ref LOADED entries + // until none remain. This handles dependency chains where an entry + // (e.g. a model) holds refs on child entries (mesh, texture): dispose + // parents first so child ref counts drop to zero, then pick up the + // children on the next pass. Without this, a forward-only scan fails + // when a shared child entry appears before a parent that still holds a + // ref to it. + bool_t any; do { - if(entry->type != ASSET_LOADER_TYPE_NULL) { - errorChain(assetEntryDispose(entry)); - } - entry++; - } while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX); + any = false; + assetentry_t *e = ASSET.entries; + do { + if(e->type == ASSET_LOADER_TYPE_NULL) { e++; continue; } + if(e->state != ASSET_ENTRY_STATE_LOADED) { e++; continue; } + if(e->refs.count > 0) { e++; continue; } + errorChain(assetEntryDispose(e)); + any = true; + e++; + } while(e < ASSET.entries + ASSET_ENTRY_COUNT_MAX); + } while(any); // Cleanup zip file. if(ASSET.zip != NULL) { diff --git a/src/dusk/asset/loader/assetloader.c b/src/dusk/asset/loader/assetloader.c index 902731b6..ab1ba41b 100644 --- a/src/dusk/asset/loader/assetloader.c +++ b/src/dusk/asset/loader/assetloader.c @@ -16,6 +16,12 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = { .dispose = assetMeshDispose }, + [ASSET_LOADER_TYPE_MODEL] = { + .loadSync = assetModelLoaderSync, + .loadAsync = assetModelLoaderAsync, + .dispose = assetModelDispose + }, + [ASSET_LOADER_TYPE_TEXTURE] = { .loadSync = assetTextureLoaderSync, .loadAsync = assetTextureLoaderAsync, diff --git a/src/dusk/asset/loader/assetloader.h b/src/dusk/asset/loader/assetloader.h index 7a96963c..a9edd1b1 100644 --- a/src/dusk/asset/loader/assetloader.h +++ b/src/dusk/asset/loader/assetloader.h @@ -7,6 +7,7 @@ #pragma once #include "asset/loader/dmf/assetmeshloader.h" +#include "asset/loader/dmf/assetmodelloader.h" #include "asset/loader/display/assettextureloader.h" #include "asset/loader/display/assettilesetloader.h" #include "asset/loader/locale/assetlocaleloader.h" @@ -17,6 +18,7 @@ typedef enum { ASSET_LOADER_TYPE_NULL, ASSET_LOADER_TYPE_MESH, + ASSET_LOADER_TYPE_MODEL, ASSET_LOADER_TYPE_TEXTURE, ASSET_LOADER_TYPE_TILESET, ASSET_LOADER_TYPE_LOCALE, @@ -28,6 +30,7 @@ typedef enum { typedef union { assetmeshloaderloading_t mesh; + assetmodelloaderloading_t model; assettextureloaderloading_t texture; assettilesetloaderloading_t tileset; assetlocaleloaderloading_t locale; @@ -37,6 +40,7 @@ typedef union { typedef union { assetmeshoutput_t mesh; + assetmodeloutput_t model; assettextureoutput_t texture; assettilesetoutput_t tileset; assetlocaleoutput_t locale; diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.c b/src/dusk/asset/loader/chunk/assetchunkloader.c index 56fadf49..408be108 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.c +++ b/src/dusk/asset/loader/chunk/assetchunkloader.c @@ -100,8 +100,8 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) { ) { nameLen++; } - memoryCopy(out->meshNames[m], data + offset, nameLen); - out->meshNames[m][nameLen] = '\0'; + memoryCopy(out->modelNames[m], data + offset, nameLen); + out->modelNames[m][nameLen] = '\0'; offset += nameLen + 1; memoryCopy( diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.h b/src/dusk/asset/loader/chunk/assetchunkloader.h index 8504b2c2..775a28cd 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.h +++ b/src/dusk/asset/loader/chunk/assetchunkloader.h @@ -34,7 +34,7 @@ typedef struct { typedef struct { tile_t tiles[CHUNK_TILE_COUNT]; uint8_t meshCount; - char_t meshNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; + char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; } assetchunkoutput_t; @@ -51,7 +51,7 @@ errorret_t assetChunkLoaderAsync(assetloading_t *loading); /** * Synchronous loader for chunk assets. Validates the DCF binary previously * read by the async phase and populates the output assetchunkoutput_t with - * tile data and DMF mesh names. + * tile data and model paths. * * @param loading Loading information for the asset being loaded. * @return Error code indicating success or failure of the load operation. diff --git a/src/dusk/asset/loader/dmf/CMakeLists.txt b/src/dusk/asset/loader/dmf/CMakeLists.txt index c767bc3f..7210daa0 100644 --- a/src/dusk/asset/loader/dmf/CMakeLists.txt +++ b/src/dusk/asset/loader/dmf/CMakeLists.txt @@ -6,4 +6,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC assetmeshloader.c + assetmodelloader.c ) diff --git a/src/dusk/asset/loader/dmf/assetmodelloader.c b/src/dusk/asset/loader/dmf/assetmodelloader.c new file mode 100644 index 00000000..ce87f96d --- /dev/null +++ b/src/dusk/asset/loader/dmf/assetmodelloader.c @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "assetmodelloader.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "util/string.h" +#include "asset/asset.h" +#include "asset/loader/assetloading.h" +#include "asset/loader/assetentry.h" +#include "asset/loader/json/assetjsonloader.h" +#include "display/texture/texture.h" +#include "yyjson.h" + +errorret_t assetModelLoaderAsync(assetloading_t *loading) { + assertNotNull(loading, "Loading cannot be NULL"); + assertNotMainThread("Should be called from an async thread."); + errorOk(); +} + +errorret_t assetModelLoaderSync(assetloading_t *loading) { + assertNotNull(loading, "Loading cannot be NULL"); + assertTrue(loading->type == ASSET_LOADER_TYPE_MODEL, "Invalid type."); + assertIsMainThread("Must be called from the main thread."); + + switch(loading->loading.model.state) { + case ASSET_MODEL_LOADING_STATE_INITIAL: + loading->loading.model.state = ASSET_MODEL_LOADING_STATE_LOCK_ASSETS; + loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; + errorOk(); + break; + + case ASSET_MODEL_LOADING_STATE_LOCK_ASSETS: { + // Lock the model's JSON descriptor as a JSON sub-asset. The entry + // key is prefixed with "json:" to avoid a type-collision with the + // model entry itself (both share the same filename). The JSON loader + // reads the real file path supplied in the input. + char_t jsonKey[ASSET_FILE_NAME_MAX]; + stringFormat( + jsonKey, sizeof(jsonKey), "json:%s", loading->entry->name + ); + assetloaderinput_t jsonInput; + memoryZero(&jsonInput, sizeof(jsonInput)); + stringCopy( + jsonInput.json.path, loading->entry->name, ASSET_FILE_NAME_MAX + ); + assetentry_t *jsonEntry = assetLock( + jsonKey, ASSET_LOADER_TYPE_JSON, &jsonInput + ); + errorret_t ret = assetRequireLoaded(jsonEntry); + if(errorIsNotOk(ret)) { + assetUnlockEntry(jsonEntry); + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + errorChain(ret); + } + + yyjson_val *root = yyjson_doc_get_root(jsonEntry->data.json); + + // Parse required mesh path. + yyjson_val *meshVal = yyjson_obj_get(root, "mesh"); + if(!meshVal || !yyjson_is_str(meshVal)) { + assetUnlockEntry(jsonEntry); + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + errorThrow("Model JSON missing 'mesh' string"); + } + const char *meshStr = yyjson_get_str(meshVal); + size_t meshLen = yyjson_get_len(meshVal); + if(meshLen >= ASSET_FILE_NAME_MAX) { + assetUnlockEntry(jsonEntry); + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + errorThrow("Model mesh path exceeds max length"); + } + char_t meshName[ASSET_FILE_NAME_MAX]; + memoryCopy(meshName, meshStr, meshLen + 1); + + // Parse optional texture path (absent = color only, no texture). + char_t textureName[ASSET_FILE_NAME_MAX]; + textureName[0] = '\0'; + yyjson_val *texVal = yyjson_obj_get(root, "texture"); + if(texVal && yyjson_is_str(texVal)) { + const char *texStr = yyjson_get_str(texVal); + size_t texLen = yyjson_get_len(texVal); + if(texLen >= ASSET_FILE_NAME_MAX) { + assetUnlockEntry(jsonEntry); + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + errorThrow("Model texture path exceeds max length"); + } + memoryCopy(textureName, texStr, texLen + 1); + } + + // Parse optional color (RGBA array 0-255, default white). + color_t color = COLOR_WHITE; + yyjson_val *colorVal = yyjson_obj_get(root, "color"); + if(colorVal && yyjson_is_arr(colorVal)) { + uint8_t ch[4] = {255, 255, 255, 255}; + size_t colorIdx, colorLen; + yyjson_val *colorElem; + yyjson_arr_foreach(colorVal, colorIdx, colorLen, colorElem) { + if(colorIdx >= 4) break; + if(yyjson_is_int(colorElem)) { + ch[colorIdx] = (uint8_t)yyjson_get_int(colorElem); + } + } + color.r = ch[0]; + color.g = ch[1]; + color.b = ch[2]; + color.a = ch[3]; + } + + // Release JSON entry; all needed fields are now in local variables. + assetUnlockEntry(jsonEntry); + + // Lock and load the mesh sub-asset. + assetentry_t *meshEntry = assetLock(meshName, ASSET_LOADER_TYPE_MESH, NULL); + ret = assetRequireLoaded(meshEntry); + if(errorIsNotOk(ret)) { + assetUnlockEntry(meshEntry); + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + errorChain(ret); + } + + // Lock and load the optional texture sub-asset. + assetentry_t *texEntry = NULL; + if(textureName[0] != '\0') { + assetloaderinput_t texInput = { .texture = TEXTURE_FORMAT_RGBA }; + texEntry = assetLock(textureName, ASSET_LOADER_TYPE_TEXTURE, &texInput); + ret = assetRequireLoaded(texEntry); + if(errorIsNotOk(ret)) { + assetUnlockEntry(texEntry); + assetUnlockEntry(meshEntry); + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + errorChain(ret); + } + } + + assetmodeloutput_t *out = &loading->entry->data.model; + out->meshEntry = meshEntry; + out->texEntry = texEntry; + out->color = color; + loading->entry->state = ASSET_ENTRY_STATE_LOADED; + errorOk(); + break; + } + + default: + errorOk(); + } + + errorOk(); +} + +errorret_t assetModelDispose(assetentry_t *entry) { + assertNotNull(entry, "Entry cannot be NULL"); + assertTrue(entry->type == ASSET_LOADER_TYPE_MODEL, "Invalid type."); + assertIsMainThread("Must be called from the main thread."); + + assetmodeloutput_t *out = &entry->data.model; + if(out->meshEntry != NULL) { + assetUnlockEntry(out->meshEntry); + out->meshEntry = NULL; + } + if(out->texEntry != NULL) { + assetUnlockEntry(out->texEntry); + out->texEntry = NULL; + } + errorOk(); +} diff --git a/src/dusk/asset/loader/dmf/assetmodelloader.h b/src/dusk/asset/loader/dmf/assetmodelloader.h new file mode 100644 index 00000000..555407c6 --- /dev/null +++ b/src/dusk/asset/loader/dmf/assetmodelloader.h @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "display/color.h" + +typedef struct assetloading_s assetloading_t; +typedef struct assetentry_s assetentry_t; + +typedef enum { + ASSET_MODEL_LOADING_STATE_INITIAL, + ASSET_MODEL_LOADING_STATE_LOCK_ASSETS +} assetmodelloadingstate_t; + +typedef struct { + assetmodelloadingstate_t state; +} assetmodelloaderloading_t; + +typedef struct { + assetentry_t *meshEntry; + assetentry_t *texEntry; + color_t color; +} assetmodeloutput_t; + +/** + * Async loader stub for model assets. Model loading is performed entirely + * on the main thread; this callback is never reached. + * + * @param loading Loading information for the asset being loaded. + * @returns Always success. + */ +errorret_t assetModelLoaderAsync(assetloading_t *loading); + +/** + * Synchronous loader for model assets. Locks the JSON descriptor file as + * a sub-asset, waits for it to load, parses the mesh path, optional + * texture path, and color tint, then releases the JSON entry. Locks and + * waits for mesh and optional texture sub-assets sequentially to stay + * within ASSET_LOADING_COUNT_MAX slot limits. + * + * @param loading Loading information for the asset being loaded. + * @returns Error code indicating success or failure. + */ +errorret_t assetModelLoaderSync(assetloading_t *loading); + +/** + * Disposer for model assets. Releases the locks on the mesh and texture + * sub-asset entries. + * + * @param entry Asset entry containing the model to dispose. + * @returns Error code indicating success or failure. + */ +errorret_t assetModelDispose(assetentry_t *entry); diff --git a/src/dusk/asset/loader/json/assetjsonloader.c b/src/dusk/asset/loader/json/assetjsonloader.c index c1d3ea9a..c8505cca 100644 --- a/src/dusk/asset/loader/json/assetjsonloader.c +++ b/src/dusk/asset/loader/json/assetjsonloader.c @@ -22,9 +22,11 @@ errorret_t assetJsonLoaderAsync(assetloading_t *loading) { assertNull(loading->loading.json.buffer, "Buffer already defined?"); assetfile_t *file = &loading->loading.json.file; - assetLoaderErrorChain(loading, - assetFileInit(file, loading->entry->name, NULL, NULL) - ); + const char_t *filePath = ( + loading->entry->input != NULL && + loading->entry->input->json.path[0] != '\0' + ) ? loading->entry->input->json.path : loading->entry->name; + assetLoaderErrorChain(loading, assetFileInit(file, filePath, NULL, NULL)); if(file->size > ASSET_JSON_FILE_SIZE_MAX) { assetLoaderErrorThrow(loading, "JSON exceeds maximum allowed size"); diff --git a/src/dusk/asset/loader/json/assetjsonloader.h b/src/dusk/asset/loader/json/assetjsonloader.h index 4508064d..e3c2d251 100644 --- a/src/dusk/asset/loader/json/assetjsonloader.h +++ b/src/dusk/asset/loader/json/assetjsonloader.h @@ -14,7 +14,16 @@ typedef struct assetloading_s assetloading_t; typedef struct assetentry_s assetentry_t; -typedef struct { void *nothing; } assetjsonloaderinput_t; +/** + * Optional file path override. When path[0] is non-zero the JSON loader + * reads this path from the archive instead of loading->entry->name. Use + * this to decouple the asset pool key from the actual file location (e.g. + * when a parent loader locks a JSON entry under a synthetic key to avoid + * a type-collision with an entry of the same name but different type). + */ +typedef struct { + char_t path[ASSET_FILE_NAME_MAX]; +} assetjsonloaderinput_t; typedef enum { ASSET_JSON_LOADING_STATE_INITIAL, diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index e0acabba..fe6e6119 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -17,7 +17,7 @@ console_t CONSOLE; void consoleInit(void) { memoryZero(&CONSOLE, sizeof(console_t)); - CONSOLE.visible = false; + CONSOLE.visible = true; #ifdef DUSK_CONSOLE_POSIX threadMutexInit(&CONSOLE.printMutex); diff --git a/src/dusk/rpg/overworld/chunk.h b/src/dusk/rpg/overworld/chunk.h index 23348819..38cc9abe 100644 --- a/src/dusk/rpg/overworld/chunk.h +++ b/src/dusk/rpg/overworld/chunk.h @@ -22,10 +22,10 @@ typedef struct chunk_s { assetentry_t *dcfEntry; uint8_t meshCount; - char_t meshNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; + char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; mat4 meshModels[CHUNK_MESH_COUNT_MAX]; - assetentry_t *meshEntries[CHUNK_MESH_COUNT_MAX]; + assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX]; uint8_t entities[CHUNK_ENTITY_COUNT_MAX]; } chunk_t; diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index 1f279a12..db12600f 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -148,9 +148,9 @@ void mapChunkUnload(chunk_t *chunk) { } for(uint8_t m = 0; m < chunk->meshCount; m++) { - if(chunk->meshEntries[m] == NULL) continue; - assetUnlockEntry(chunk->meshEntries[m]); - chunk->meshEntries[m] = NULL; + if(chunk->modelEntries[m] == NULL) continue; + assetUnlockEntry(chunk->modelEntries[m]); + chunk->modelEntries[m] = NULL; } chunk->meshCount = 0; } @@ -183,6 +183,7 @@ errorret_t mapChunkLoad(chunk_t *chunk) { } errorret_t mapWaitForChunks() { + // Phase 1: Wait for all DCF entries to finish loading. bool_t anyPending; do { anyPending = false; @@ -190,76 +191,70 @@ errorret_t mapWaitForChunks() { for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { chunk_t *chunk = &MAP.chunks[i]; + if(chunk->dcfEntry == NULL) continue; - if(chunk->dcfEntry != NULL) { - assetentrystate_t state = chunk->dcfEntry->state; + assetentrystate_t state = chunk->dcfEntry->state; + if(state == ASSET_ENTRY_STATE_LOADED) { + uint8_t meshCount = chunk->dcfEntry->data.chunk.meshCount; + memoryCopy( + chunk->tiles, + chunk->dcfEntry->data.chunk.tiles, + sizeof(chunk->tiles) + ); + worldpos_t wp; + chunkPosToWorldPos(&chunk->position, &wp); + vec3 wpf = { (float_t)wp.x, (float_t)wp.y, (float_t)wp.z }; - if(state == ASSET_ENTRY_STATE_LOADED) { - uint8_t meshCount = chunk->dcfEntry->data.chunk.meshCount; - memoryCopy( - chunk->tiles, - chunk->dcfEntry->data.chunk.tiles, - sizeof(chunk->tiles) + for(uint8_t m = 0; m < meshCount; m++) { + stringCopy( + chunk->modelNames[m], + chunk->dcfEntry->data.chunk.modelNames[m], + CHUNK_MESH_NAME_MAX ); - worldpos_t wp; - chunkPosToWorldPos(&chunk->position, &wp); - vec3 wpf = { (float_t)wp.x, (float_t)wp.y, (float_t)wp.z }; - - for(uint8_t m = 0; m < meshCount; m++) { - stringCopy( - chunk->meshNames[m], - chunk->dcfEntry->data.chunk.meshNames[m], - CHUNK_MESH_NAME_MAX - ); - glm_vec3_copy( - chunk->dcfEntry->data.chunk.meshOffsets[m], - chunk->meshOffsets[m] - ); - vec3 pos; - glm_vec3_add(wpf, chunk->meshOffsets[m], pos); - glm_translate_make(chunk->meshModels[m], pos); - } - assetUnlockEntry(chunk->dcfEntry); - chunk->dcfEntry = NULL; - - for(uint8_t m = 0; m < meshCount; m++) { - chunk->meshEntries[m] = assetLock( - chunk->meshNames[m], ASSET_LOADER_TYPE_MESH, NULL - ); - if(chunk->meshEntries[m] == NULL) { - for(uint8_t j = 0; j < m; j++) { - assetUnlockEntry(chunk->meshEntries[j]); - chunk->meshEntries[j] = NULL; - } - errorThrow("Failed to lock mesh: %s", chunk->meshNames[m]); - } - } - chunk->meshCount = meshCount; - // Fall through to the mesh-state check below so cached meshes - // don't force an unnecessary extra sleep iteration. - - } else if(state == ASSET_ENTRY_STATE_ERROR) { - assetUnlockEntry(chunk->dcfEntry); - chunk->dcfEntry = NULL; - memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles)); - continue; - } else { - anyPending = true; - continue; - } - } - - for(uint8_t m = 0; m < chunk->meshCount; m++) { - if(chunk->meshEntries[m] == NULL) continue; - if(chunk->meshEntries[m]->state != ASSET_ENTRY_STATE_LOADED) { - anyPending = true; - break; + glm_vec3_copy( + chunk->dcfEntry->data.chunk.meshOffsets[m], + chunk->meshOffsets[m] + ); + vec3 pos; + glm_vec3_add(wpf, chunk->meshOffsets[m], pos); + glm_translate_make(chunk->meshModels[m], pos); } + assetUnlockEntry(chunk->dcfEntry); + chunk->dcfEntry = NULL; + chunk->meshCount = meshCount; + } else if(state == ASSET_ENTRY_STATE_ERROR) { + assetUnlockEntry(chunk->dcfEntry); + chunk->dcfEntry = NULL; + memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles)); + } else { + anyPending = true; } } if(anyPending) usleep(1000); } while(anyPending); + + // Phase 2: Load model entries one at a time. + // Models are locked and waited on sequentially so that only one model + // entry occupies a loading slot at a time. This leaves the remaining + // slots free for the model's mesh and texture sub-assets, preventing + // the slot-exhaustion deadlock that occurs when multiple models are + // assigned slots simultaneously and each blocks waiting for sub-assets + // that have nowhere to run. + for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { + chunk_t *chunk = &MAP.chunks[i]; + for(uint8_t m = 0; m < chunk->meshCount; m++) { + if(chunk->modelEntries[m] != NULL) continue; + chunk->modelEntries[m] = assetLock( + chunk->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL + ); + if(chunk->modelEntries[m] == NULL) { + errorThrow("Failed to lock model: %s", chunk->modelNames[m]); + } + errorChain(assetRequireLoaded(chunk->modelEntries[m])); + } + } + errorOk(); } diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index 9bb91bd3..fc3bcca6 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -24,27 +24,8 @@ #include "asset/loader/assetloader.h" #include "asset/loader/assetentry.h" -#define TEXTURE_CHUNK_SIZE 16 - -static texture_t TEXTURE_CHUNK; -static color_t TEXTURE_CHUNK_PIXELS[TEXTURE_CHUNK_SIZE * TEXTURE_CHUNK_SIZE]; - errorret_t sceneOverworldInit(scenedata_t *sceneData) { assertNotNull(sceneData, "Scene data cannot be null"); - - for(uint32_t i = 0; i < TEXTURE_CHUNK_SIZE * TEXTURE_CHUNK_SIZE; i++) { - uint8_t r = (uint8_t)((i & 7) * 36); - uint8_t g = (uint8_t)(((i >> 3) & 7) * 36); - uint8_t b = (uint8_t)((i >> 6) * 85); - TEXTURE_CHUNK_PIXELS[i] = color4b(r, g, b, 255); - } - errorChain(textureInit( - &TEXTURE_CHUNK, - TEXTURE_CHUNK_SIZE, TEXTURE_CHUNK_SIZE, - TEXTURE_FORMAT_RGBA, - (texturedata_t){ .rgbaColors = TEXTURE_CHUNK_PIXELS } - )); - errorOk(); } @@ -138,25 +119,31 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { } errorret_t sceneOverworldDrawChunks() { - shadermaterial_t chunkMaterial = { - .unlit = { - .color = COLOR_WHITE, - .texture = &TEXTURE_CHUNK - } - }; - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { chunk_t *chunk = &MAP.chunks[i]; if(chunk->meshCount == 0) continue; for(uint8_t m = 0; m < chunk->meshCount; m++) { - if(chunk->meshEntries[m] == NULL) continue; + if(chunk->modelEntries[m] == NULL) continue; + if(chunk->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) continue; + assetmodeloutput_t *model = &chunk->modelEntries[m]->data.model; + if(model->meshEntry == NULL) continue; + + texture_t *tex = model->texEntry != NULL + ? &model->texEntry->data.texture + : NULL; + + shadermaterial_t mat = { + .unlit = { .color = model->color, .texture = tex } + }; errorChain(shaderSetMatrix( &SHADER_UNLIT, SHADER_UNLIT_MODEL, chunk->meshModels[m] )); - errorChain(shaderSetMaterial(&SHADER_UNLIT, &chunkMaterial)); - errorChain(meshDraw(&chunk->meshEntries[m]->data.mesh.mesh, 0, -1)); + errorChain(shaderSetMaterial(&SHADER_UNLIT, &mat)); + errorChain(meshDraw( + &model->meshEntry->data.mesh.mesh, 0, -1 + )); } } @@ -171,8 +158,5 @@ errorret_t sceneOverworldDrawChunks() { errorret_t sceneOverworldDispose(scenedata_t *sceneData) { assertNotNull(sceneData, "Scene data cannot be null"); - - errorChain(textureDispose(&TEXTURE_CHUNK)); - errorOk(); } diff --git a/tools/asset/chunk/__main__.py b/tools/asset/chunk/__main__.py index 36111e44..a0d01b93 100644 --- a/tools/asset/chunk/__main__.py +++ b/tools/asset/chunk/__main__.py @@ -26,8 +26,8 @@ JSON input (assetsraw/chunks/chunk_X_Y_Z.json): Version 3 DCF format (after 8-byte header + tiles): uint8_t meshCount - for each mesh: - null-terminated string (relative asset path to .dmf) + for each model: + null-terminated string (relative asset path to .json model) float32[3] (x, y, z offset) DMF format: @@ -89,11 +89,11 @@ def tile_index(x, y, z): return x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT -def find_mesh(filename): - """Search ASSETS_DIR/meshes/ recursively for filename. +def find_model(filename): + """Search ASSETS_DIR/models/ recursively for filename. Returns the path relative to ASSETS_DIR (forward slashes), or None.""" - meshes_root = os.path.join(ASSETS_DIR, 'meshes') - for dirpath, _, filenames in os.walk(meshes_root): + models_root = os.path.join(ASSETS_DIR, 'models') + for dirpath, _, filenames in os.walk(models_root): if filename in filenames: rel = os.path.relpath( os.path.join(dirpath, filename), ASSETS_DIR @@ -102,6 +102,17 @@ def find_mesh(filename): return None +def write_model_json(path, mesh_rel, texture_rel, color): + """Write a model JSON descriptor file.""" + obj = {'mesh': mesh_rel, 'color': color} + if texture_rel: + obj['texture'] = texture_rel + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w') as f: + json.dump(obj, f, indent=2) + print(f' Wrote model JSON {path}') + + def derive_dcf_path(json_path): """assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf""" base = os.path.splitext(os.path.basename(json_path))[0] @@ -228,31 +239,45 @@ def from_json(json_path, dcf_path): ) terrain_verts = build_terrain_verts(tiles) - mesh_names = [] + model_names = [] mesh_offsets = [] - # Terrain mesh (only written if non-empty) dcf_base = os.path.splitext(os.path.basename(dcf_path))[0] + + # Terrain mesh + model (only written if non-empty) terrain_dir = os.path.join(ASSETS_DIR, 'meshes', 'chunks') + models_dir = os.path.join(ASSETS_DIR, 'models', 'chunks') os.makedirs(terrain_dir, exist_ok=True) + os.makedirs(models_dir, exist_ok=True) if terrain_verts: dmf_name = f'chunk_{dcf_base}_0.dmf' write_dmf(os.path.join(terrain_dir, dmf_name), terrain_verts) - mesh_names.append(f'meshes/chunks/{dmf_name}') + mesh_rel = f'meshes/chunks/{dmf_name}' + json_name = f'chunk_{dcf_base}_0.json' + write_model_json( + os.path.join(models_dir, json_name), + mesh_rel, + 'tiles.png', + [255, 255, 255, 255] + ) + model_names.append(f'models/chunks/{json_name}') mesh_offsets.append((0.0, 0.0, 0.0)) - # External mesh references + # External mesh references -> look up matching model JSON for mesh in data.get('meshes', []): filename = mesh['file'] pos = mesh.get('pos', [0, 0, 0]) - rel = find_mesh(filename) + model_filename = os.path.splitext(filename)[0] + '.json' + rel = find_model(model_filename) if rel is None: - raise ValueError(f"Mesh not found under assets/meshes/: {filename}") - mesh_names.append(rel) + raise ValueError( + f"Model not found under assets/models/: {model_filename}" + ) + model_names.append(rel) mesh_offsets.append((float(pos[0]), float(pos[1]), float(pos[2]))) print(f' Resolved {filename} -> {rel}') - write_v3(dcf_path, bytes(tiles), mesh_names, mesh_offsets) + write_v3(dcf_path, bytes(tiles), model_names, mesh_offsets) def process_json(json_path):