aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOsCoder <oscoder@lczimmermann.eu>2026-07-22 23:21:40 +0200
committerOsCoder <oscoder@lczimmermann.eu>2026-07-22 23:21:40 +0200
commit400b321f8799f369cf26cf611462598117004ced (patch)
tree85bc676bd110edc57c2faa7461f23b5eb1099cc1
downloadsolar_map-400b321f8799f369cf26cf611462598117004ced.tar.xz
solar_map-400b321f8799f369cf26cf611462598117004ced.zip
initial commit
-rw-r--r--.editorconfig8
-rw-r--r--.gitattributes8
-rw-r--r--.gitignore27
-rw-r--r--LICENSE.txt287
-rw-r--r--README.md20
-rw-r--r--galaxy.binbin0 -> 1536 bytes
-rw-r--r--galaxy.bmpbin0 -> 3456054 bytes
-rw-r--r--galaxy.csv25
-rw-r--r--render/render.v157
-rw-r--r--solar_map.v168
-rw-r--r--solar_map_test.v28
-rw-r--r--v.mod7
12 files changed, 735 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..01072ca
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.v]
+indent_style = tab
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..9a98968
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+* text=auto eol=lf
+*.bat eol=crlf
+
+*.v linguist-language=V
+*.vv linguist-language=V
+*.vsh linguist-language=V
+v.mod linguist-language=V
+.vdocignore linguist-language=ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f0d06f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Binaries for programs and plugins
+main
+solar_map
+*.exe
+*.exe~
+*.so
+*.dylib
+*.dll
+
+# Ignore binary output folders
+bin/
+
+# Ignore common editor/system specific metadata
+.DS_Store
+.idea/
+.vscode/
+*.iml
+
+# ENV
+.env
+
+# Web assets and local databases
+*.db
+*.js
+
+# Ignore installed modules through `v install --local`:
+modules/
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..4153cd3
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,287 @@
+ EUROPEAN UNION PUBLIC LICENCE v. 1.2
+ EUPL © the European Union 2007, 2016
+
+This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
+below) which is provided under the terms of this Licence. Any use of the Work,
+other than as authorised under this Licence is prohibited (to the extent such
+use is covered by a right of the copyright holder of the Work).
+
+The Work is provided under the terms of this Licence when the Licensor (as
+defined below) has placed the following notice immediately following the
+copyright notice for the Work:
+
+ Licensed under the EUPL
+
+or has expressed by any other means his willingness to license under the EUPL.
+
+1. Definitions
+
+In this Licence, the following terms have the following meaning:
+
+- ‘The Licence’: this Licence.
+
+- ‘The Original Work’: the work or software distributed or communicated by the
+ Licensor under this Licence, available as Source Code and also as Executable
+ Code as the case may be.
+
+- ‘Derivative Works’: the works or software that could be created by the
+ Licensee, based upon the Original Work or modifications thereof. This Licence
+ does not define the extent of modification or dependence on the Original Work
+ required in order to classify a work as a Derivative Work; this extent is
+ determined by copyright law applicable in the country mentioned in Article 15.
+
+- ‘The Work’: the Original Work or its Derivative Works.
+
+- ‘The Source Code’: the human-readable form of the Work which is the most
+ convenient for people to study and modify.
+
+- ‘The Executable Code’: any code which has generally been compiled and which is
+ meant to be interpreted by a computer as a program.
+
+- ‘The Licensor’: the natural or legal person that distributes or communicates
+ the Work under the Licence.
+
+- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
+ Licence, or otherwise contributes to the creation of a Derivative Work.
+
+- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
+ the Work under the terms of the Licence.
+
+- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
+ renting, distributing, communicating, transmitting, or otherwise making
+ available, online or offline, copies of the Work or providing access to its
+ essential functionalities at the disposal of any other natural or legal
+ person.
+
+2. Scope of the rights granted by the Licence
+
+The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
+sublicensable licence to do the following, for the duration of copyright vested
+in the Original Work:
+
+- use the Work in any circumstance and for all usage,
+- reproduce the Work,
+- modify the Work, and make Derivative Works based upon the Work,
+- communicate to the public, including the right to make available or display
+ the Work or copies thereof to the public and perform publicly, as the case may
+ be, the Work,
+- distribute the Work or copies thereof,
+- lend and rent the Work or copies thereof,
+- sublicense rights in the Work or copies thereof.
+
+Those rights can be exercised on any media, supports and formats, whether now
+known or later invented, as far as the applicable law permits so.
+
+In the countries where moral rights apply, the Licensor waives his right to
+exercise his moral right to the extent allowed by law in order to make effective
+the licence of the economic rights here above listed.
+
+The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
+any patents held by the Licensor, to the extent necessary to make use of the
+rights granted on the Work under this Licence.
+
+3. Communication of the Source Code
+
+The Licensor may provide the Work either in its Source Code form, or as
+Executable Code. If the Work is provided as Executable Code, the Licensor
+provides in addition a machine-readable copy of the Source Code of the Work
+along with each copy of the Work that the Licensor distributes or indicates, in
+a notice following the copyright notice attached to the Work, a repository where
+the Source Code is easily and freely accessible for as long as the Licensor
+continues to distribute or communicate the Work.
+
+4. Limitations on copyright
+
+Nothing in this Licence is intended to deprive the Licensee of the benefits from
+any exception or limitation to the exclusive rights of the rights owners in the
+Work, of the exhaustion of those rights or of other applicable limitations
+thereto.
+
+5. Obligations of the Licensee
+
+The grant of the rights mentioned above is subject to some restrictions and
+obligations imposed on the Licensee. Those obligations are the following:
+
+Attribution right: The Licensee shall keep intact all copyright, patent or
+trademarks notices and all notices that refer to the Licence and to the
+disclaimer of warranties. The Licensee must include a copy of such notices and a
+copy of the Licence with every copy of the Work he/she distributes or
+communicates. The Licensee must cause any Derivative Work to carry prominent
+notices stating that the Work has been modified and the date of modification.
+
+Copyleft clause: If the Licensee distributes or communicates copies of the
+Original Works or Derivative Works, this Distribution or Communication will be
+done under the terms of this Licence or of a later version of this Licence
+unless the Original Work is expressly distributed only under this version of the
+Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
+(becoming Licensor) cannot offer or impose any additional terms or conditions on
+the Work or Derivative Work that alter or restrict the terms of the Licence.
+
+Compatibility clause: If the Licensee Distributes or Communicates Derivative
+Works or copies thereof based upon both the Work and another work licensed under
+a Compatible Licence, this Distribution or Communication can be done under the
+terms of this Compatible Licence. For the sake of this clause, ‘Compatible
+Licence’ refers to the licences listed in the appendix attached to this Licence.
+Should the Licensee's obligations under the Compatible Licence conflict with
+his/her obligations under this Licence, the obligations of the Compatible
+Licence shall prevail.
+
+Provision of Source Code: When distributing or communicating copies of the Work,
+the Licensee will provide a machine-readable copy of the Source Code or indicate
+a repository where this Source will be easily and freely available for as long
+as the Licensee continues to distribute or communicate the Work.
+
+Legal Protection: This Licence does not grant permission to use the trade names,
+trademarks, service marks, or names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the copyright notice.
+
+6. Chain of Authorship
+
+The original Licensor warrants that the copyright in the Original Work granted
+hereunder is owned by him/her or licensed to him/her and that he/she has the
+power and authority to grant the Licence.
+
+Each Contributor warrants that the copyright in the modifications he/she brings
+to the Work are owned by him/her or licensed to him/her and that he/she has the
+power and authority to grant the Licence.
+
+Each time You accept the Licence, the original Licensor and subsequent
+Contributors grant You a licence to their contributions to the Work, under the
+terms of this Licence.
+
+7. Disclaimer of Warranty
+
+The Work is a work in progress, which is continuously improved by numerous
+Contributors. It is not a finished work and may therefore contain defects or
+‘bugs’ inherent to this type of development.
+
+For the above reason, the Work is provided under the Licence on an ‘as is’ basis
+and without warranties of any kind concerning the Work, including without
+limitation merchantability, fitness for a particular purpose, absence of defects
+or errors, accuracy, non-infringement of intellectual property rights other than
+copyright as stated in Article 6 of this Licence.
+
+This disclaimer of warranty is an essential part of the Licence and a condition
+for the grant of any rights to the Work.
+
+8. Disclaimer of Liability
+
+Except in the cases of wilful misconduct or damages directly caused to natural
+persons, the Licensor will in no event be liable for any direct or indirect,
+material or moral, damages of any kind, arising out of the Licence or of the use
+of the Work, including without limitation, damages for loss of goodwill, work
+stoppage, computer failure or malfunction, loss of data or any commercial
+damage, even if the Licensor has been advised of the possibility of such damage.
+However, the Licensor will be liable under statutory product liability laws as
+far such laws apply to the Work.
+
+9. Additional agreements
+
+While distributing the Work, You may choose to conclude an additional agreement,
+defining obligations or services consistent with this Licence. However, if
+accepting obligations, You may act only on your own behalf and on your sole
+responsibility, not on behalf of the original Licensor or any other Contributor,
+and only if You agree to indemnify, defend, and hold each Contributor harmless
+for any liability incurred by, or claims asserted against such Contributor by
+the fact You have accepted any warranty or additional liability.
+
+10. Acceptance of the Licence
+
+The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
+placed under the bottom of a window displaying the text of this Licence or by
+affirming consent in any other similar way, in accordance with the rules of
+applicable law. Clicking on that icon indicates your clear and irrevocable
+acceptance of this Licence and all of its terms and conditions.
+
+Similarly, you irrevocably accept this Licence and all of its terms and
+conditions by exercising any rights granted to You by Article 2 of this Licence,
+such as the use of the Work, the creation by You of a Derivative Work or the
+Distribution or Communication by You of the Work or copies thereof.
+
+11. Information to the public
+
+In case of any Distribution or Communication of the Work by means of electronic
+communication by You (for example, by offering to download the Work from a
+remote location) the distribution channel or media (for example, a website) must
+at least provide to the public the information requested by the applicable law
+regarding the Licensor, the Licence and the way it may be accessible, concluded,
+stored and reproduced by the Licensee.
+
+12. Termination of the Licence
+
+The Licence and the rights granted hereunder will terminate automatically upon
+any breach by the Licensee of the terms of the Licence.
+
+Such a termination will not terminate the licences of any person who has
+received the Work from the Licensee under the Licence, provided such persons
+remain in full compliance with the Licence.
+
+13. Miscellaneous
+
+Without prejudice of Article 9 above, the Licence represents the complete
+agreement between the Parties as to the Work.
+
+If any provision of the Licence is invalid or unenforceable under applicable
+law, this will not affect the validity or enforceability of the Licence as a
+whole. Such provision will be construed or reformed so as necessary to make it
+valid and enforceable.
+
+The European Commission may publish other linguistic versions or new versions of
+this Licence or updated versions of the Appendix, so far this is required and
+reasonable, without reducing the scope of the rights granted by the Licence. New
+versions of the Licence will be published with a unique version number.
+
+All linguistic versions of this Licence, approved by the European Commission,
+have identical value. Parties can take advantage of the linguistic version of
+their choice.
+
+14. Jurisdiction
+
+Without prejudice to specific agreement between parties,
+
+- any litigation resulting from the interpretation of this License, arising
+ between the European Union institutions, bodies, offices or agencies, as a
+ Licensor, and any Licensee, will be subject to the jurisdiction of the Court
+ of Justice of the European Union, as laid down in article 272 of the Treaty on
+ the Functioning of the European Union,
+
+- any litigation arising between other parties and resulting from the
+ interpretation of this License, will be subject to the exclusive jurisdiction
+ of the competent court where the Licensor resides or conducts its primary
+ business.
+
+15. Applicable Law
+
+Without prejudice to specific agreement between parties,
+
+- this Licence shall be governed by the law of the European Union Member State
+ where the Licensor has his seat, resides or has his registered office,
+
+- this licence shall be governed by Belgian law if the Licensor has no seat,
+ residence or registered office inside a European Union Member State.
+
+Appendix
+
+‘Compatible Licences’ according to Article 5 EUPL are:
+
+- GNU General Public License (GPL) v. 2, v. 3
+- GNU Affero General Public License (AGPL) v. 3
+- Open Software License (OSL) v. 2.1, v. 3.0
+- Eclipse Public License (EPL) v. 1.0
+- CeCILL v. 2.0, v. 2.1
+- Mozilla Public Licence (MPL) v. 2
+- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
+- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
+ works other than software
+- European Union Public Licence (EUPL) v. 1.1, v. 1.2
+- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
+ Reciprocity (LiLiQ-R+).
+
+The European Commission may update this Appendix to later versions of the above
+licences without producing a new version of the EUPL, as long as they provide
+the rights granted in Article 2 of this Licence and protect the covered Source
+Code from exclusive appropriation.
+
+All other changes or additions to this Appendix require the production of a new
+EUPL version.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b477add
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# Solar Map
+
+A fast and lightweight V module that reads astronomical dataset records from CSV, encodes them into a portable fixed-size binary format, and renders the spatial distribution to a 24-bit BMP image.
+
+## Features
+
+- **Fast Memory Dump**: Serializes and deserializes spatial objects (`SpaceBody`) using a custom 64-byte binary layout.
+- **Cross-Platform & Safe**: Uses explicit Little-Endian byte-shifting (no `unsafe` blocks), making the binary output completely safe and compatible across different architectures (x86_64, ARM64, RISC-V).
+- **Non-Linear Visual Rendering**: Renders positional data into a 24-bit BMP bitmap with square-root distance scaling to clearly separate both inner and outer space bodies.
+
+## Project Structure
+
+```text
+.
+├── main.v # Application entry point & binary file IO
+├── galaxy.csv # Source CSV dataset
+├── galaxy.bin # Generated binary dataset (created on run)
+├── galaxy.bmp # Output image (created on run)
+└── render/
+ └── render.v # Module for BMP rendering and distance scaling
diff --git a/galaxy.bin b/galaxy.bin
new file mode 100644
index 0000000..7242b58
--- /dev/null
+++ b/galaxy.bin
Binary files differ
diff --git a/galaxy.bmp b/galaxy.bmp
new file mode 100644
index 0000000..d8070ac
--- /dev/null
+++ b/galaxy.bmp
Binary files differ
diff --git a/galaxy.csv b/galaxy.csv
new file mode 100644
index 0000000..6bee13e
--- /dev/null
+++ b/galaxy.csv
@@ -0,0 +1,25 @@
+id,parent_id,rel_x,rel_y,rel_z,radius,type,flags,comment
+1,0,0,0,0,696340000,1,0,Sonne (Zentrum)
+2,1,57909050000,0,0,2439700,2,0,Merkur
+3,1,108208000000,0,0,6051800,2,0,Venus
+4,1,149597870700,0,0,6371000,2,0,Erde
+5,4,384400000,0,0,1737400,3,0,Mond
+6,1,227939200000,0,0,3389500,2,0,Mars
+7,6,9376000,0,0,11267,3,0,Phobos
+8,6,23463200,0,0,6200,3,0,Deimos
+9,1,413700000000,0,0,469730,4,0,Ceres (Zwergplanet)
+10,1,778570000000,0,0,69911000,2,0,Jupiter
+11,10,421700000,0,0,1821600,3,0,Io
+12,10,670900000,0,0,1560800,3,0,Europa
+13,10,1070400000,0,0,2634100,3,0,Ganymed
+14,10,1882700000,0,0,2410300,3,0,Kallisto
+15,1,1433530000000,0,0,58232000,2,0,Saturn
+16,15,1221870000,0,0,2574700,3,0,Titan
+17,15,238000000,0,0,252100,3,0,Enceladus
+18,1,2870972000000,0,0,25362000,2,0,Uranus
+19,18,435910000,0,0,788400,3,0,Titania
+20,1,4500000000000,0,0,24622000,2,0,Neptun
+21,20,354759000,0,0,1353400,3,0,Triton
+22,1,5906380000000,0,0,1188300,4,0,Pluto (Zwergplanet)
+23,22,19571000,0,0,606000,3,0,Charon
+24,1,10120000000000,0,0,1163000,4,0,Eris (Zwergplanet)
diff --git a/render/render.v b/render/render.v
new file mode 100644
index 0000000..910e524
--- /dev/null
+++ b/render/render.v
@@ -0,0 +1,157 @@
+module render
+
+import os
+import math
+
+@[packed]
+pub struct SpaceBody {
+pub mut:
+ id u32
+ parent_id u32
+ rel_x i64
+ rel_y i64
+ rel_z i64
+ radius u32
+ body_type u8
+ flags [3]u8
+ comment [24]u8
+}
+
+struct Color {
+ r u8
+ g u8
+ b u8
+}
+
+fn get_abs_x(id u32, bodies map[u32]SpaceBody) i64 {
+ if id == 0 || id !in bodies {
+ return 0
+ }
+ body := bodies[id]
+ if body.parent_id == 0 {
+ return body.rel_x
+ }
+ return body.rel_x + get_abs_x(body.parent_id, bodies)
+}
+
+pub fn draw_bodies_to_bmp(bodies []SpaceBody, output_path string, width int, height int) ! {
+ center_y := height / 2
+
+ mut body_map := map[u32]SpaceBody{}
+ for body in bodies {
+ body_map[body.id] = body
+ }
+
+ mut max_abs_x := f64(1)
+ for body in bodies {
+ abs_x := math.abs(f64(get_abs_x(body.id, body_map)))
+ if abs_x > max_abs_x {
+ max_abs_x = abs_x
+ }
+ }
+
+ mut pixels := []u8{len: width * height * 3, init: 10}
+
+ set_pixel := fn (mut px []u8, w int, h int, x int, y int, color Color) {
+ if x >= 0 && x < w && y >= 0 && y < h {
+ idx := (y * w + x) * 3
+ px[idx] = color.r
+ px[idx + 1] = color.g
+ px[idx + 2] = color.b
+ }
+ }
+
+ for body in bodies {
+ abs_x := f64(get_abs_x(body.id, body_map))
+
+ norm_x := math.sqrt(abs_x / max_abs_x)
+ px_x := int(50 + norm_x * f64(width - 100))
+
+ mut col := Color{r: 255, g: 255, b: 255}
+ mut draw_radius := 3
+
+ match body.body_type {
+ 1 {
+ col = Color{r: 255, g: 220, b: 50}
+ draw_radius = 12
+ }
+ 2 {
+ col = Color{r: 100, g: 180, b: 255}
+ draw_radius = 5
+ }
+ 3 {
+ col = Color{r: 180, g: 180, b: 180}
+ draw_radius = 2
+ }
+ 4 {
+ col = Color{r: 200, g: 130, b: 100}
+ draw_radius = 3
+ }
+ else {}
+ }
+
+ for dy := -draw_radius; dy <= draw_radius; dy++ {
+ for dx := -draw_radius; dx <= draw_radius; dx++ {
+ if dx * dx + dy * dy <= draw_radius * draw_radius {
+ set_pixel(mut pixels, width, height, px_x + dx, center_y + dy, col)
+ }
+ }
+ }
+ }
+
+ save_bmp(output_path, width, height, pixels)!
+}
+
+fn save_bmp(path string, width int, height int, rgb_pixels []u8) ! {
+ file_header_size := 14
+ info_header_size := 40
+ row_padding := (4 - (width * 3) % 4) % 4
+ pixel_array_size := (width * 3 + row_padding) * height
+ file_size := file_header_size + info_header_size + pixel_array_size
+
+ mut buf := []u8{cap: file_size}
+
+ // BMP Header
+ buf << `B`
+ buf << `M`
+ buf << u32_to_bytes(u32(file_size))
+ buf << [u8(0), 0, 0, 0]
+ buf << u32_to_bytes(u32(file_header_size + info_header_size))
+
+ // DIB Header
+ buf << u32_to_bytes(u32(info_header_size))
+ buf << i32_to_bytes(i32(width))
+ buf << i32_to_bytes(i32(-height))
+ buf << [u8(1), 0]
+ buf << [u8(24), 0]
+ buf << u32_to_bytes(0)
+ buf << u32_to_bytes(u32(pixel_array_size))
+ buf << u32_to_bytes(2835)
+ buf << u32_to_bytes(2835)
+ buf << u32_to_bytes(0)
+ buf << u32_to_bytes(0)
+
+ // Pixeldaten (BGR-Reihenfolge)
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ idx := (y * width + x) * 3
+ buf << rgb_pixels[idx + 2] // B
+ buf << rgb_pixels[idx + 1] // G
+ buf << rgb_pixels[idx] // R
+ }
+ for p := 0; p < row_padding; p++ {
+ buf << u8(0)
+ }
+ }
+
+ os.write_file(path, buf.bytestr())!
+}
+
+fn u32_to_bytes(val u32) []u8 {
+ return [u8(val & 0xFF), u8((val >> 8) & 0xFF), u8((val >> 16) & 0xFF), u8((val >> 24) & 0xFF)]
+}
+
+fn i32_to_bytes(val i32) []u8 {
+ u := u32(val)
+ return [u8(u & 0xFF), u8((u >> 8) & 0xFF), u8((u >> 16) & 0xFF), u8((u >> 24) & 0xFF)]
+}
diff --git a/solar_map.v b/solar_map.v
new file mode 100644
index 0000000..eea0faf
--- /dev/null
+++ b/solar_map.v
@@ -0,0 +1,168 @@
+module solar_map
+
+import os
+import time
+import render
+
+pub type SpaceBody = render.SpaceBody
+
+// Convert a SpaceBody into a 64-byte Little-Endian array
+fn body_to_bytes(b SpaceBody) []u8 {
+ mut bytes := []u8{cap: 64}
+
+ bytes << u32_to_bytes(b.id)
+ bytes << u32_to_bytes(b.parent_id)
+ bytes << i64_to_bytes(b.rel_x)
+ bytes << i64_to_bytes(b.rel_y)
+ bytes << i64_to_bytes(b.rel_z)
+ bytes << u32_to_bytes(b.radius)
+ bytes << b.body_type
+ bytes << b.flags[0]
+ bytes << b.flags[1]
+ bytes << b.flags[2]
+
+ for i in 0 .. 24 {
+ bytes << b.comment[i]
+ }
+
+ return bytes
+}
+
+// Reconstruct a SpaceBody from a 64-byte slice
+fn body_from_bytes(data []u8) SpaceBody {
+ mut flags_arr := [3]u8{}
+ flags_arr[0] = data[37]
+ flags_arr[1] = data[38]
+ flags_arr[2] = data[39]
+
+ mut comment_arr := [24]u8{}
+ for i in 0 .. 24 {
+ comment_arr[i] = data[40 + i]
+ }
+
+ return SpaceBody{
+ id: bytes_to_u32(data[0..4])
+ parent_id: bytes_to_u32(data[4..8])
+ rel_x: bytes_to_i64(data[8..16])
+ rel_y: bytes_to_i64(data[16..24])
+ rel_z: bytes_to_i64(data[24..32])
+ radius: bytes_to_u32(data[32..36])
+ body_type: data[36]
+ flags: flags_arr
+ comment: comment_arr
+ }
+}
+
+// Public API function to process a CSV file, output binary data, and optionally render a BMP image
+pub fn process_galaxy(csv_path string, generate_image bool) ! {
+ struct_size := int(sizeof(SpaceBody))
+
+ if !os.exists(csv_path) {
+ return error('File not found: ${csv_path}')
+ }
+
+ // 1. Parse CSV
+ sw_parse := time.new_stopwatch()
+ lines := os.read_lines(csv_path)!
+
+ mut bodies := []SpaceBody{cap: lines.len - 1}
+
+ for i := 1; i < lines.len; i++ {
+ line := lines[i].trim_space()
+ if line == '' {
+ continue
+ }
+
+ parts := line.split(',')
+ if parts.len < 7 {
+ continue
+ }
+
+ mut flag_val := u8(0)
+ if parts.len >= 8 {
+ flag_val = parts[7].u8()
+ }
+
+ mut body := SpaceBody{
+ id: parts[0].u32()
+ parent_id: parts[1].u32()
+ rel_x: parts[2].i64()
+ rel_y: parts[3].i64()
+ rel_z: parts[4].i64()
+ radius: parts[5].u32()
+ body_type: parts[6].u8()
+ flags: [flag_val, u8(0), u8(0)]!
+ }
+
+ if parts.len >= 9 {
+ comment_str := parts[8].trim_space()
+ mut bytes_to_copy := comment_str.len
+ if bytes_to_copy > 24 {
+ bytes_to_copy = 24
+ }
+ for j := 0; j < bytes_to_copy; j++ {
+ body.comment[j] = comment_str[j]
+ }
+ }
+
+ bodies << body
+ }
+ println('CSV parsed in: ${sw_parse.elapsed().microseconds()} µs')
+
+ // 2. Write binary data
+ bin_path := csv_path.replace('.csv', '.bin')
+ mut bytes_to_write := []u8{cap: bodies.len * struct_size}
+ for body in bodies {
+ bytes_to_write << body_to_bytes(body)
+ }
+ os.write_file(bin_path, bytes_to_write.bytestr())!
+
+ // 3. Load binary data back (Fast Load validation)
+ sw_load := time.new_stopwatch()
+ bin_bytes := os.read_bytes(bin_path)!
+ body_count := bin_bytes.len / struct_size
+
+ mut loaded_bodies := []SpaceBody{cap: body_count}
+ for i in 0 .. body_count {
+ offset := i * struct_size
+ loaded_bodies << body_from_bytes(bin_bytes[offset .. offset + struct_size])
+ }
+ println('Binary loaded in: ${sw_load.elapsed().microseconds()} µs')
+
+ // 4. Render BMP image if requested
+ if generate_image {
+ bmp_path := csv_path.replace('.csv', '.bmp')
+ sw_render := time.new_stopwatch()
+ render.draw_bodies_to_bmp(loaded_bodies, bmp_path, 1920, 600)!
+ println('Image successfully rendered to "${bmp_path}" in ${sw_render.elapsed().microseconds()} µs')
+ }
+}
+
+// Helper functions for Little-Endian conversions
+fn u32_to_bytes(v u32) []u8 {
+ return [u8(v), u8(v >> 8), u8(v >> 16), u8(v >> 24)]
+}
+
+fn i64_to_bytes(v i64) []u8 {
+ u := u64(v)
+ return [
+ u8(u),
+ u8(u >> 8),
+ u8(u >> 16),
+ u8(u >> 24),
+ u8(u >> 32),
+ u8(u >> 40),
+ u8(u >> 48),
+ u8(u >> 56),
+ ]
+}
+
+fn bytes_to_u32(b []u8) u32 {
+ return u32(b[0]) | (u32(b[1]) << 8) | (u32(b[2]) << 16) | (u32(b[3]) << 24)
+}
+
+fn bytes_to_i64(b []u8) i64 {
+ u := u64(b[0]) | (u64(b[1]) << 8) | (u64(b[2]) << 16) | (u64(b[3]) << 24) |
+ (u64(b[4]) << 32) | (u64(b[5]) << 40) | (u64(b[6]) << 48) | (u64(b[7]) << 56)
+ return i64(u)
+}
diff --git a/solar_map_test.v b/solar_map_test.v
new file mode 100644
index 0000000..6d2d8d9
--- /dev/null
+++ b/solar_map_test.v
@@ -0,0 +1,28 @@
+module solar_map
+
+import os
+
+fn test_process_galaxy() {
+ // Erstelle eine kleine temporäre Test-CSV
+ test_csv := 'test_galaxy.csv'
+ test_bin := 'test_galaxy.bin'
+ test_bmp := 'test_galaxy.bmp'
+
+ csv_content := 'id,parent_id,rel_x,rel_y,rel_z,radius,type,flags,comment
+1,0,0,0,0,696340000,1,0,Sun
+2,1,57909050000,0,0,2439700,2,0,Merkur'
+
+ os.write_file(test_csv, csv_content) or { assert false }
+
+ // Ausführen der öffentlichen Funktion
+ process_galaxy(test_csv, true) or { assert false }
+
+ // Überprüfen, ob die Output-Dateien erzeugt wurden
+ assert os.exists(test_bin)
+ assert os.exists(test_bmp)
+
+ // Aufräumen
+ os.rm(test_csv) or {}
+ os.rm(test_bin) or {}
+ os.rm(test_bmp) or {}
+}
diff --git a/v.mod b/v.mod
new file mode 100644
index 0000000..244d705
--- /dev/null
+++ b/v.mod
@@ -0,0 +1,7 @@
+Module {
+ name: 'solar_map'
+ description: 'A high-performance V utility to convert spatial body CSV data into a binary format and render it to a BMP image.'
+ version: '0.1.0'
+ license: 'EUPL-1.2'
+ dependencies: []
+}