WIP Build system
This commit is contained in:
52
.vscode/development_status/Development_Status_2025-10-12.md
vendored
Normal file
52
.vscode/development_status/Development_Status_2025-10-12.md
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
# Project "Millimeters of Aluminium" Development Log
|
||||
## Overview
|
||||
|
||||
The project is undergoing a major architectural refactor to move from a monolithic Spaceship class to a fully modular, component-based system. The foundation for this new architecture, centered around Module, Component, and Station classes, is now in place. The next steps involve migrating legacy systems into this new paradigm.
|
||||
|
||||
## I. Fully Implemented & Stable Systems
|
||||
|
||||
Custom Physics Core: All physical objects now inherit from a custom OrbitalBody2D class, which handles mass aggregation and force integration. The physics loop is correctly disabled in the editor to prevent errors.
|
||||
|
||||
Modular Ship Construction:
|
||||
|
||||
Module as Root: The Module class now serves as the root for ship assemblies, managing its own list of structural pieces and components without needing container nodes.
|
||||
|
||||
Builder Plugin: The editor plugin is updated to work with this new architecture, allowing the placement of StructuralPiece nodes directly onto Module nodes.
|
||||
|
||||
Character & Interaction Foundation:
|
||||
|
||||
Zero-G Movement: The PilotBall character has a state machine for handling movement inside ship interiors, including sluggish zero-G floating and direct control on ladders.
|
||||
|
||||
Generic Station Component: A StationComponent class has been implemented. It serves as a generic hardware terminal that characters can interact with.
|
||||
|
||||
Data-Driven UI Architecture:
|
||||
|
||||
Databank Resource: A Databank Resource class has been created. It acts as "software," holding a reference to a UI scene that can be loaded by a station.
|
||||
|
||||
## II. Work-In-Progress (WIP) and Planned Systems
|
||||
|
||||
This list details systems we have designed but are not yet fully implemented in the code.
|
||||
|
||||
System Migration to Databanks:
|
||||
|
||||
Helm/Flight Controls: The logic from the old ThrusterController.gd needs to be moved into a HelmUI.tscn scene and driven by a HelmDatabank.
|
||||
|
||||
Navigation Computer: The UI has been moved, but the extensive planning and calculation logic from NavigationComputer.gd needs to be transferred to NavUI.gd.
|
||||
|
||||
Fuel and Life Support: The FuelSystem and LifeSupport nodes are still part of the old Spaceship.tscn. They need to be fully redesigned as Component classes (e.g., FuelTank, AtmosphereProcessor).
|
||||
|
||||
Component Wiring System:
|
||||
|
||||
Signal/Socket Advertising: Components and Databanks need to be updated with get_input_sockets() and get_output_signals() functions.
|
||||
|
||||
Wiring Data Storage: The Module class needs a wiring_data array to store the connections made in the builder.
|
||||
|
||||
Builder UI: A visual wiring interface needs to be added to the module builder plugin.
|
||||
|
||||
Orbital Stability Test:
|
||||
|
||||
Ghost Simulator: A GhostSimulator class needs to be created to run predictive, in-memory physics calculations.
|
||||
|
||||
Test Runner: An orbital_stability_test.tscn scene and script are needed to orchestrate the test, compare live vs. ghost results, and generate a report.
|
||||
|
||||
Full Spaceship Class Retirement: The final step will be to delete Spaceship.tscn and Spaceship.gd once all their logic and systems have been successfully migrated to the new modular architecture.
|
||||
78
.vscode/development_status/Development_Status_2025-10-14.md
vendored
Normal file
78
.vscode/development_status/Development_Status_2025-10-14.md
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
## Development Progress Report 14/10 - 25
|
||||
### Overview
|
||||
The project has successfully undergone a foundational architectural refactor. The legacy monolithic Spaceship class has been deprecated in favor of a fully modular, component-based architecture designed for multiplayer scalability. The core gameplay loop of a player spawning, possessing a character, using a station, and controlling a ship's systems via a diegetic UI is now functional. The project is now entering "Cycle 2" of development, focusing on unifying the physics system and migrating the remaining legacy gameplay logic into the new architecture.
|
||||
|
||||
### ✅ Implemented Systems & Features
|
||||
#### 1. Core Architecture
|
||||
Modular Ships: Ships are now built around a root Module class which extends OrbitalBody2D. The Module dynamically understands its structure by finding its Component and StructuralPiece children, removing the need for rigid container nodes.
|
||||
|
||||
Custom Physics Body: The OrbitalBody2D class serves as the base for all physical objects in the simulation, including ship parts and modules. It correctly handles force routing from child components to the root physics body and includes a robust, deferred check to warn if a child class forgets to call super() in its _ready function.
|
||||
|
||||
Dynamic Inertia Calculation: The ship's moment of inertia is now calculated realistically based on the mass and distribution of all its component parts in local space, leading to more authentic rotational physics.
|
||||
|
||||
#### 2. Player Control & Multiplayer Foundation
|
||||
PlayerController/Pawn Architecture: A multiplayer-ready control scheme has been implemented.
|
||||
|
||||
The PlayerController class is responsible for capturing raw input and sending it to the server via RPCs.
|
||||
|
||||
The PilotBall (the "Pawn") is now a "dumb" character that only acts on commands received from its controller, with all direct calls to the Input singleton successfully removed.
|
||||
|
||||
Dynamic Spawning & Possession: The GameManager now manages the game's startup sequence. It dynamically spawns a PlayerController and a default ship (Tube.tscn), and then correctly "possesses" the PilotBall within the ship with its corresponding controller.
|
||||
|
||||
Local Server Initialization: The GameManager correctly initializes a local ENet server, which enables the multiplayer authority system (is_multiplayer_authority()) to function correctly in a single-player testing environment.
|
||||
|
||||
#### 3. Station & UI Systems
|
||||
Modular UI Framework: The "databank" system has been fully implemented and separated into three distinct resource types:
|
||||
|
||||
ControlPanel: A resource representing a physical UI element (e.g., a screen, a lever).
|
||||
|
||||
Databank: A resource representing a "datashard" which contains pure logic in a script.
|
||||
|
||||
SystemStation: The physical station component that acts as a "chassis," hosting panels and databanks.
|
||||
|
||||
Persistent Station Logic: The SystemStation has been refactored to instantiate datashard logic (_ready) once for its entire lifetime, allowing for background processing. UI Panels are created ephemerally only when a player occupies the station.
|
||||
|
||||
Functional Helm: The helm is partially migrated.
|
||||
|
||||
A HelmLogicShard contains the attitude-hold (PD controller) and calibration logic.
|
||||
|
||||
ControlPanels for a throttle lever, buttons, and a status readout are functional.
|
||||
|
||||
The station correctly wires the panels to the helm shard at runtime, allowing the player to control the ship's main engine and RCS thrusters.
|
||||
|
||||
Functional Sensor Display: The sensor/map system is partially migrated.
|
||||
|
||||
A SensorSystemShard is responsible for gathering all trackable bodies in the system.
|
||||
|
||||
A refactored SensorPanel acts as a "dumb" display that visualizes the "sensor feed" signal it receives.
|
||||
|
||||
The ShipStatusShard correctly displays the ship's velocity and rotational data on the ReadoutScreen and also displays the currently selected target from the map.
|
||||
|
||||
### ❌ Not Yet Implemented / Pending Tasks
|
||||
#### 1. Physics & Simulation (Cycle 2 Priority)
|
||||
|
||||
CelestialBody Refactor: All celestial bodies (Planets, Moons, etc.) still inherit from Godot's RigidBody2D and use the _integrate_forces callback. They must be refactored to extend our custom OrbitalBody2D to create a single, stable physics simulation.
|
||||
|
||||
Astronomical vs. Ship Scale: A system for scaling forces needs to be designed and implemented to allow massive planets and lightweight ships to coexist and interact realistically within the same simulation.
|
||||
|
||||
Simulation Stability Test: The proposed GhostSimulator and test runner for verifying long-term orbital stability has not yet been created.
|
||||
|
||||
Interior Physics: The simulation does not yet account for how the ship's acceleration affects characters or loose objects inside it.
|
||||
|
||||
#### 2. System & Feature Migration
|
||||
|
||||
Full Helm/Nav Migration: The complex maneuver planning logic (e.g., Hohmann transfers) still resides in the legacy navigation_computer.gd script and needs to be migrated into one or more Databank shards.
|
||||
|
||||
Thruster Refactor: The Thruster component still uses a simple turn_on()/turn_off() model. It needs to be refactored to accept a variable set_throttle(value) command for more precise control.
|
||||
|
||||
Retirement of Legacy Scenes/Scripts: The old Spaceship.tscn, spaceship.gd, thruster_controller.gd, and navigation_computer.gd files are still in the project and need to be fully removed once their logic is migrated.
|
||||
|
||||
#### 3. Planned Features (Future Cycles)
|
||||
Wiring System: The foundational classes exist, but the in-game system for players to visually wire panels to databanks, and the editor tools to support this, have not been started.
|
||||
|
||||
Character & Movement: The current PilotBall is a placeholder. The planned humanoid character, grapple points, and EVA gameplay are not yet implemented.
|
||||
|
||||
Core Gameplay Systems: The foundational systems for Electricity, Life Support (pressurization), Fuel, Character Damage, and Inventory/Pickupable Objects have not yet been created.
|
||||
|
||||
Multiplayer Connectivity: While the architecture supports it, the UI and logic for multiple players to connect to a server (e.g., a main menu with a "Join Game" option) do not yet exist.
|
||||
52
.vscode/development_status/Development_Status_2025-10-16.md
vendored
Normal file
52
.vscode/development_status/Development_Status_2025-10-16.md
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
## Project Development Status Update 16/10 - 25
|
||||
### Overview
|
||||
The project has successfully completed a major architectural refactor, establishing a stable and scalable foundation for the simulation. The core physics model has been unified under a custom OrbitalBody2D class and a hierarchical Barycenter system, which has resolved previous orbital instabilities. The ship's control systems are being migrated to a flexible, data-driven "databank" architecture, and the UI is now managed by a robust grid-based layout system. The focus can now shift to migrating the remaining legacy systems and building out core gameplay features on this new foundation.
|
||||
|
||||
### ✅ Implemented Systems & Features
|
||||
#### 1. Hierarchical Physics Simulation (Barycenter Architecture)
|
||||
Global & Local Grids: The simulation is now anchored by a StarSystem root node, which defines the global grid. Procedurally generated planetary systems are encapsulated within Barycenter nodes, which act as moving "local grids" for their contents. This has stabilized the orbits of moons and planets.
|
||||
|
||||
Physics Roles: A clear distinction has been made between physics actors and passive bodies.
|
||||
|
||||
Barycenter nodes are the primary physics objects in the global simulation, inheriting from OrbitalBody2D and responding to gravitational forces.
|
||||
|
||||
Celestial bodies (Star, Planet, Moon) are now simple Node2Ds that provide mass data to their parent Barycenter but do not run their own physics integration, solving the "triple velocity" bug.
|
||||
|
||||
Centralized Physics Loop: All gravity calculations are now managed by the OrbitalMechanics singleton in a multi-stage _physics_process loop, which handles global (Barycenter-to-Star) and local (Moon-to-Planet) interactions separately.
|
||||
|
||||
#### 2. Procedural Generation & Player Spawn
|
||||
Generator as a Tool: The StarSystemGenerator has been refactored into a RefCounted class that acts as a factory, cleanly separating the generation process from the final StarSystem product.
|
||||
|
||||
Stable Orbit Placement: The generator now uses astrophysical concepts like the Roche Limit and Hill Sphere (abstracted into helper functions in OrbitalMechanics) to procedurally place planets and moons in stable, non-overlapping orbits.
|
||||
|
||||
Lagrange Point Spawning: The player ship is now correctly spawned at the L4 or L5 Lagrange point of the outermost planet, with the proper initial velocity to maintain a stable position.
|
||||
|
||||
#### 3. Data-Driven Ship Systems (Databanks)
|
||||
Autopilot Migration: The core logic for planning and executing maneuvers has been successfully migrated from the legacy ThrusterController into a series of decoupled databank shards:
|
||||
|
||||
NavSelectionDatabank: Stores the current navigation target.
|
||||
|
||||
ManeuverPlannerDatabank: Calculates maneuver burn plans (e.g., Hohmann transfers).
|
||||
|
||||
AutopilotDatabank: Executes the steps of a received plan.
|
||||
|
||||
Modular UI Layout: The SystemStation now functions as a layout manager, instancing and positioning UI panels based on grid data defined in ControlPanel resources. This has removed hardcoded positions and allows for flexible, data-driven UI configurations.
|
||||
|
||||
#### 4. Orbit Projection & Debugging
|
||||
Unified Projection Function: The OrbitalMechanics library now contains a single, generalized project_n_body_paths function. This function can run a "ghost simulation" on any arbitrary set of bodies in either local or global space to generate predictive orbital paths for the map panel.
|
||||
|
||||
Orrery View: A dedicated debugging tool, the OrreryView scene, has been created to provide a clean, interactive chart for inspecting procedurally generated star systems without the interference of game UI or camera logic.
|
||||
|
||||
### ⏳ Planned & Discussed Future Implementations
|
||||
#### 1. Advanced Physics Optimization
|
||||
Centralized N-Body Calculation: The plan is to have the OrbitalMechanics singleton manage all gravity calculations in a single, authoritative loop each frame. This will enable advanced optimizations and debugging, such as a "force queue" to prevent calculation errors.
|
||||
|
||||
Sphere of Influence (SOI) Model: For dynamic objects like the player's ship, we will implement an SOI system. The ship will calculate its gravity against the full system hierarchy when in "deep space" but will switch to calculating against only the local bodies (e.g., a planet and its moons) when it enters a Barycenter's sphere of influence.
|
||||
|
||||
Performance Culling & Caching: For performance-intensive scenarios like asteroid belts, we've discussed implementing timers to cache and reuse negligible force calculations over several frames, only recalculating when necessary.
|
||||
|
||||
#### 2. Component "API" & Wiring System
|
||||
Component Contracts: To facilitate the upcoming visual wiring system, we will formalize the "API" for ControlPanel and Databank resources. This will be done by creating new scripts that extend the base classes and override the get_input_sockets() and get_output_signals() functions to explicitly define what signals and functions each component provides.
|
||||
|
||||
Static vs. Resource-Based API: We've concluded that using extended Resource scripts to define these APIs is superior to using static functions on the node scripts. This decouples the data contract from the implementation and allows a single scene to be used with multiple different data configurations, which is critical for a flexible wiring system.
|
||||
64
.vscode/development_status/Development_Status_2025-10-25.md
vendored
Normal file
64
.vscode/development_status/Development_Status_2025-10-25.md
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
## Project Development Status Update: 31/10/25
|
||||
|
||||
### 3D Character Controller & Movement Tech Demo (Cycle 3)
|
||||
|
||||
Work has proceeded on a tech demo for the 3D character controller, establishing a robust, physics-based system for zero-G movement. The architecture has been refactored to prioritize a clean separation of concerns, with a central "pawn" acting as a physics integrator and modular "controllers" acting as the "brains" for different movement types.
|
||||
|
||||
### ✅ Implemented Features
|
||||
|
||||
#### Pawn/Controller Architecture: The character is split into several key classes:
|
||||
|
||||
CharacterPawn3D: The core CharacterBody3D. It acts as a "dumb" physics integrator, holding velocity and angular_velocity, integrating rotation, and calling move_and_slide(). It no longer contains movement-specific state logic.
|
||||
|
||||
PlayerController3D: Gathers all hardware input (keyboard, mouse) and packages it into KeyInput dictionaries (pressed, held, released) to send to the pawn via RPC.
|
||||
|
||||
EVAMovementComponent: Refactored into a "dumb tool". It exposes functions like apply_thrusters() and apply_orientation() which are called by other controllers.
|
||||
|
||||
ZeroGMovementComponent: This is now the "brain" for all zero-G movement. It receives all inputs from the pawn and contains its own internal state machine (IDLE, REACHING, GRIPPING, CLIMBING, CHARGING_LAUNCH).
|
||||
|
||||
#### Contextual Movement Logic:
|
||||
|
||||
The ZeroGMovementComponent decides when to use the EVA suit. In its IDLE state, it checks for fresh movement input (movement_input_was_neutral) before calling the EVAMovementComponent's apply_thrusters function.
|
||||
|
||||
This successfully implements "coast on release," where releasing a grip (_release_current_grip) flags the movement input as "stale," preventing the EVA suit from engaging even if the key is still held.
|
||||
|
||||
#### EVA/Jetpack Controls:
|
||||
|
||||
The EVAMovementComponent provides force-based linear movement (WASD, Shift/Ctrl) and torque-based angular roll (Q/E).
|
||||
|
||||
A body-orientation function (_orient_pawn) allows the pawn to auto-align with the camera's forward direction.
|
||||
|
||||
#### Physics-Based Grip System:
|
||||
|
||||
GripArea3D: A composition-based Area3D node provides the interface for all grabbable objects. It requires its parent to implement functions like get_grip_transform and get_push_off_normal.
|
||||
|
||||
Grip Detection: The CharacterPawn3D uses a GripDetector Area3D to find GripArea3D nodes in range and passes this nearby_grips list to the ZeroGMovementComponent.
|
||||
|
||||
GRIPPING State: This state is now fully physics-based. Instead of setting the pawn's global_transform, the _apply_grip_physics function uses a PD controller to apply linear forces (to move to the offset position) and angular torques (to align with the grip's orientation).
|
||||
|
||||
Grip Orientation: The gripping logic correctly calculates the closest of two opposing orientations (e.g., "up" or "down" on a bar) by comparing the pawn's current up vector to the grip's potential up vectors.
|
||||
|
||||
Grip Rolling: While in the GRIPPING state, the player can use Q/E to override the auto-orientation and apply roll torque around the grip's axis.
|
||||
|
||||
#### Physics-Based Climbing:
|
||||
|
||||
CLIMBING State: This state applies lerp'd velocity to move the pawn, allowing it to interact with physics.
|
||||
|
||||
Climb Targeting: The _find_best_grip function successfully identifies the next valid grip within a configurable climb_angle_threshold_deg cone.
|
||||
|
||||
Handover: Logic in _process_climbing correctly identifies when the pawn is close enough to the next_grip_target to _perform_grip_handover.
|
||||
|
||||
Climb Release: The pawn will correctly release its grip and enter the IDLE state (coasting) if it moves past the current_grip by release_past_grip_threshold without a new target being found.
|
||||
|
||||
### ❌ Not Yet Implemented / Pending Tasks
|
||||
|
||||
REACHING State: The REACHING state exists but its logic (_process_reaching) is a stub that instantly calls _try_initiate_reach. The full implementation (e.g., procedural animation/IK moving the hand to the target) is pending.
|
||||
|
||||
CHARGING_LAUNCH State: The state exists and the execution logic is present (_handle_launch_charge, _execute_launch), but the state transition logic in _update_state does not currently allow entering this state from GRIPPING (it's overshadowed by the _start_climb check).
|
||||
|
||||
Ladder (3D) & Walking (3D) States: The CharacterPawn3D has high-level states for GRIPPING_LADDER and WALKING, but the movement functions (_apply_ladder_movement, _apply_walking_movement) are stubs.
|
||||
|
||||
Generic Surface Grab: The TODO to allow the ZeroGMovementComponent to grab any physics surface (not just a GripArea3D) is not implemented.
|
||||
|
||||
EVA Stabilization: The _apply_stabilization_torques function in EVAMovementComponent is still a placeholder.
|
||||
89
.vscode/development_status/Development_Status_2025-11-21.md
vendored
Normal file
89
.vscode/development_status/Development_Status_2025-11-21.md
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
# Development Status Report - 2025-11-21
|
||||
|
||||
## Overview
|
||||
|
||||
Significant progress was made on the Unified Build System and the In-Game Builder, moving away from purely editor-based construction. We successfully implemented procedural geometry generation for structural pieces and refactored the snapping logic to support the upcoming geodesic structures. Networking logic also saw crucial stability fixes.
|
||||
|
||||
## Completed Features & Implementations
|
||||
|
||||
### 1. Unified Build System Foundation
|
||||
|
||||
- StructureData Resource: Implemented a robust resource-based definition system for ship parts. This decouples the "DNA" of a part (mesh, collider, mounts, health) from its scene instantiation, allowing shared logic between the Editor Plugin and the In-Game Builder.
|
||||
|
||||
- PieceMount Logic: Formalized attachment points into a dedicated PieceMount class (inheriting Area3D). This replaced the ad-hoc dictionary system, providing type safety and better collision filtering.
|
||||
|
||||
- ProceduralPiece Generator: Created a script that dynamically generates 3D meshes and collision shapes based on parameters (e.g., triangle vs. square, size, thickness) defined in StructureData. This supports:
|
||||
|
||||
- Extruded 3D geometry (thickness) rather than flat planes.
|
||||
|
||||
- Convex collision hull generation.
|
||||
|
||||
- "Opaque Blueprint" preview materials (semi-transparent, emissive).
|
||||
|
||||
### 2. In-Game Builder (PlayerController3D)
|
||||
|
||||
- Build Mode State: Implemented a toggleable Build Mode (B key) in PlayerController3D.
|
||||
|
||||
- Piece Selection: Added logic to select pieces via hotkeys (1 = Square, 2 = Triangle), instantiate a preview "ghost," and switch its material.
|
||||
|
||||
- Raycast Snapping: Implemented a physics-based raycast/sweep to detect existing ship modules and mounts.
|
||||
|
||||
- Visual Feedback: Added color-coded feedback for the preview ghost:
|
||||
|
||||
- Cyan: Floating (no snap target found).
|
||||
|
||||
- Geen: Snapped (aligned with a valid mount).
|
||||
|
||||
### 3. Snapping Logic Refactor
|
||||
|
||||
- SnappingTool Class: Created a dedicated static helper class for snapping math.
|
||||
|
||||
- Shape Cast Sweep: Replaced simple raycasting with a sphere_cast (radius 0.2m). This adds "thickness" to the cursor, making it much easier to hit thin structural elements like struts or small mounts.
|
||||
|
||||
- Transform Alignment: Implemented matrix math to align a new piece's mount with a target mount, respecting position, normal (facing direction), and up-vector (roll/orientation).
|
||||
|
||||
### 4. Networking Stability (Previous Session)
|
||||
|
||||
- Server Authority Enforcement: enforced strict server authority on CharacterPawn3D, removing client-side overrides that caused "fighting" and stutter.
|
||||
|
||||
- Relative Velocity Sync: Implemented logic to sync local_velocity relative to the parent ship instead of global velocity. This prevents pawns from "falling out the back" of moving ships during network jitter.
|
||||
|
||||
- Input Serialization: Fixed RPC errors by converting custom KeyInput objects to Dictionaries before transmission.
|
||||
|
||||
## Pending / In-Progress
|
||||
|
||||
- Mount Orientation Constraints: The snapping tool currently aligns normals but needs refinement to strictly enforce edge length compatibility and specific mount types (e.g., preventing a 2m edge from snapping to a 1m edge).
|
||||
|
||||
- Module Persistence: The logic for creating a new Module when building in empty space works in memory but needs testing for persistence and saving.
|
||||
|
||||
- Collision Layers: Need to verify that all PieceMount nodes are consistently on the correct physics layer (1 << 14) to ensure the snapping sweep always finds them.
|
||||
|
||||
## Discussion & Direction Changes
|
||||
|
||||
### Shift to "Geodesic & Procedural" Building
|
||||
|
||||
We moved away from the initial "Voxel Grid" concept for ship hulls.
|
||||
|
||||
- Old Direction: Ships built on a strict integer grid (Minecraft-style but with slopes).
|
||||
|
||||
- New Direction: A node-based "Geodesic" system. Pieces connect Node-to-Node (Mount-to-Mount) at arbitrary angles. This allows for complex shapes (hexagonal cylinders, spheres, rings) and supports the "Industrial/Hard Sci-Fi" aesthetic better.
|
||||
|
||||
- Implication: The building tool no longer relies on grid_step for positioning. It relies entirely on the SnappingTool to calculate transforms based on the geometry of the mounting points.
|
||||
|
||||
### Shift to "Server Authoritative" Networking
|
||||
|
||||
We abandoned the "Client Authoritative" movement for pawns to solve synchronization issues.
|
||||
|
||||
- Old Plan: Client moves pawn, Server accepts pos. (Caused hacking risks and desync with physics objects).
|
||||
|
||||
- New Plan: Server simulates physics. Client sends inputs. MultiplayerSynchronizer interpolates the result. To combat latency feel, we are using Visual Interpolation (detaching camera/mesh from the physics body) rather than full Client-Side Prediction (CSP) for this milestone.
|
||||
|
||||
### Manufacturing & Blueprints
|
||||
|
||||
We discussed that the StructureData resource is the key enabler for the manufacturing gameplay loop. By defining parts as data (Resources), we can easily:
|
||||
|
||||
1. Store a "Blueprint" as a list of StructureData references + Transforms.
|
||||
|
||||
2. Have a manufacturing machine consume resources to produce a "Crate" containing a StructureData item.
|
||||
|
||||
3. Have the player pick up that item and use it to place the ProceduralPiece.
|
||||
@ -65,6 +65,9 @@ func _enter_tree():
|
||||
_setup_button_connections()
|
||||
_update_ui_labels()
|
||||
|
||||
# Add the Tool Menu Item
|
||||
add_tool_menu_item("Generate Structure Definitions", _on_generate_structures_pressed)
|
||||
|
||||
main_screen.hide()
|
||||
|
||||
undo_redo = EditorInterface.get_editor_undo_redo()
|
||||
@ -128,6 +131,9 @@ func _exit_tree():
|
||||
if main_screen:
|
||||
main_screen.queue_free()
|
||||
|
||||
# Clean up the menu item
|
||||
remove_tool_menu_item("Generate Structure Definitions")
|
||||
|
||||
func _has_main_screen() -> bool:
|
||||
return true
|
||||
|
||||
@ -522,3 +528,28 @@ func _find_closest_attachment_point(module: Module, world_pos: Vector2):
|
||||
closest_point = point
|
||||
|
||||
return closest_point
|
||||
|
||||
const GeneratorScript = preload("res://data/structure/structure_generator.gd")
|
||||
|
||||
# The callback function
|
||||
func _on_generate_structures_pressed():
|
||||
if GeneratorScript:
|
||||
var generator = GeneratorScript.new()
|
||||
if generator.has_method("generate_system_one"):
|
||||
generator.generate_system_one()
|
||||
else:
|
||||
push_error("StructureGenerator script missing 'generate_system_one' method.")
|
||||
|
||||
if generator.has_method("generate_system_two_pentagonal"):
|
||||
generator.generate_system_two_pentagonal()
|
||||
else:
|
||||
push_error("StructureGenerator script missing 'generate_system_two_pentagonal' method.")
|
||||
|
||||
if generator.has_method("generate_system_two_v2_sphere"):
|
||||
generator.generate_system_two_v2_sphere()
|
||||
else:
|
||||
push_error("StructureGenerator script missing 'generate_system_two_v2_sphere' method.")
|
||||
|
||||
# Cleanup if it's a Node
|
||||
if generator is Node:
|
||||
generator.queue_free()
|
||||
31
src/data/structure/definitions/1m_square_dome_top.tres
Normal file
31
src/data/structure/definitions/1m_square_dome_top.tres
Normal file
@ -0,0 +1,31 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://balw2uv0dx8tw"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_ylkpk"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_ylkpk")
|
||||
piece_name = "1m Square Dome Top"
|
||||
type = 1
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 0.7869350219613372, -0.6170358751407486),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0.6170358751407486, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(-0.7869350219613372, 1.74734676053076e-16, -0.6170358751407486),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.6170358751407486, 1.370094871201985e-16, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(-1.7473467605307596e-16, -0.7869350219613372, -0.6170358751407486),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-1.3700948712019848e-16, -0.6170358751407486, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(0.7869350219613372, -2.1841834506634496e-16, -0.6170358751407486),
|
||||
"position": Vector3(0.5, -1.3877787807814457e-16, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.6170358751407486, -1.712618589002481e-16, 0.7869350219613372)
|
||||
}])
|
||||
31
src/data/structure/definitions/1m_square_flat.tres
Normal file
31
src/data/structure/definitions/1m_square_flat.tres
Normal file
@ -0,0 +1,31 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://btpl1hnsk78db"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_wlppn"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_wlppn")
|
||||
piece_name = "1m Square Flat"
|
||||
type = 1
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1, 2.2204460492503136e-16, 0),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-2.220446049250313e-16, -1, 0),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(1, -2.7755575615628914e-16, 0),
|
||||
"position": Vector3(0.5, -1.3877787807814457e-16, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
@ -1,23 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://squareplate001"]
|
||||
|
||||
[ext_resource type="Script" path="res://data/structure/structure_data.gd" id="1_script"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
piece_name = "Square Plate 1x1"
|
||||
type = 1
|
||||
base_mass = 10.0
|
||||
health_max = 100.0
|
||||
shape = "Square"
|
||||
vertices = [
|
||||
Vector3(-0.5, -0.5, 0.0),
|
||||
Vector3(-0.5, 0.5, 0.0),
|
||||
Vector3(0.5, 0.5, 0.0),
|
||||
Vector3(0.5, -0.5, 0.0)
|
||||
]
|
||||
mounts = [
|
||||
{ "position": Vector3(0, 0.5, 0), "normal": Vector3(0, 1, 0), "up": Vector3(0, 0, 1), "type": 0 },
|
||||
{ "position": Vector3(0, -0.5, 0), "normal": Vector3(0, -1, 0), "up": Vector3(0, 0, 1), "type": 0 },
|
||||
{ "position": Vector3(-0.5, 0, 0), "normal": Vector3(-1, 0, 0), "up": Vector3(0, 0, 1), "type": 0 },
|
||||
{ "position": Vector3(0.5, 0, 0), "normal": Vector3(1, 0, 0), "up": Vector3(0, 0, 1), "type": 0 }
|
||||
]
|
||||
27
src/data/structure/definitions/1m_triangle_dome_side.tres
Normal file
27
src/data/structure/definitions/1m_triangle_dome_side.tres
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://da1m4eaojir4s"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_bcg7m"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_bcg7m")
|
||||
piece_name = "1m Triangle Dome Side"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, -0.28867513459481287, 0), Vector3(0.5, -0.28867513459481287, 0), Vector3(0, 0.5773502691896257, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -0.7869350219613372, -0.6170358751407486),
|
||||
"position": Vector3(0, -0.28867513459481287, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, -0.6170358751407486, 0.7869350219613372)
|
||||
}, {
|
||||
"normal": Vector3(0.8420970529402404, 0.4861849601988384, -0.23344536385590545),
|
||||
"position": Vector3(0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.2021696154949157, 0.11672268192795272, 0.9723699203976766)
|
||||
}, {
|
||||
"normal": Vector3(-0.8420970529402404, 0.4861849601988384, -0.23344536385590545),
|
||||
"position": Vector3(-0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.2021696154949157, 0.11672268192795272, 0.9723699203976766)
|
||||
}])
|
||||
27
src/data/structure/definitions/1m_triangle_flat.tres
Normal file
27
src/data/structure/definitions/1m_triangle_flat.tres
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dlqju8f1hiepk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_44lvp"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_44lvp")
|
||||
piece_name = "1m Triangle Flat"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-4.440892098500626e-16, -1, 0),
|
||||
"position": Vector3(-1.1102230246251565e-16, -0.288675134594813, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
27
src/data/structure/definitions/1m_triangle_geo.tres
Normal file
27
src/data/structure/definitions/1m_triangle_geo.tres
Normal file
@ -0,0 +1,27 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dyvonkcjxbh4r"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_lytiu"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_lytiu")
|
||||
piece_name = "1m Triangle Geo"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(-0.8090448070910988, 0.46710223716051485, -0.35673799931962513),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.30894416990603113, 0.1783689996598126, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(-4.148701268396191e-16, -0.9342044743210295, -0.35673799931962513),
|
||||
"position": Vector3(-1.1102230246251565e-16, -0.288675134594813, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-1.584234962413445e-16, -0.35673799931962513, 0.9342044743210295)
|
||||
}])
|
||||
@ -1,21 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://triangleplate001"]
|
||||
|
||||
[ext_resource type="Script" path="res://data/structure/structure_data.gd" id="1_script"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
piece_name = "Triangle Plate 1m"
|
||||
type = 1 # PLATE
|
||||
base_mass = 5.0
|
||||
health_max = 100.0
|
||||
shape = "Triangle"
|
||||
vertices = [
|
||||
Vector3(-0.5, -0.288675, 0.0),
|
||||
Vector3(0.0, 0.57735, 0.0),
|
||||
Vector3(0.5, -0.288675, 0.0)
|
||||
]
|
||||
mounts = [
|
||||
{ "position": Vector3(0, -0.288, 0), "normal": Vector3(0, -1, 0), "up": Vector3(0, 0, 1), "type": 0 },
|
||||
{ "position": Vector3(0.25, 0.144, 0), "normal": Vector3(0.866, 0.5, 0), "up": Vector3(0, 0, 1), "type": 0 },
|
||||
{ "position": Vector3(-0.25, 0.144, 0), "normal": Vector3(-0.866, 0.5, 0), "up": Vector3(0, 0, 1), "type": 0 }
|
||||
]
|
||||
35
src/data/structure/definitions/s1_cyl12_wall.tres
Normal file
35
src/data/structure/definitions/s1_cyl12_wall.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dq7lk5e3686oh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_48w0s"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_48w0s")
|
||||
piece_name = "S 1 Cyl 12 Wall"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Rect"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, 0.5, 0), Vector3(0.5, 0.5, 0), Vector3(0.5, -0.5, 0), Vector3(-0.5, -0.5, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -1, 0),
|
||||
"position": Vector3(0, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(0, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.9659258262890683, 0, -0.25881904510252074),
|
||||
"position": Vector3(0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.25881904510252074, 0, 0.9659258262890683)
|
||||
}, {
|
||||
"normal": Vector3(0.9659258262890683, 0, -0.25881904510252074),
|
||||
"position": Vector3(-0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.25881904510252074, 0, 0.9659258262890683)
|
||||
}])
|
||||
35
src/data/structure/definitions/s1_cyl8_wall.tres
Normal file
35
src/data/structure/definitions/s1_cyl8_wall.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dlkqr2t1b52kg"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_0wpc7"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_0wpc7")
|
||||
piece_name = "S 1 Cyl 8 Wall"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Rect"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, 0.5, 0), Vector3(0.5, 0.5, 0), Vector3(0.5, -0.5, 0), Vector3(-0.5, -0.5, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -1, 0),
|
||||
"position": Vector3(0, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(0, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.9238795325112867, 0, -0.3826834323650898),
|
||||
"position": Vector3(0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.3826834323650898, 0, 0.9238795325112867)
|
||||
}, {
|
||||
"normal": Vector3(0.9238795325112867, 0, -0.3826834323650898),
|
||||
"position": Vector3(-0.5, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3826834323650898, 0, 0.9238795325112867)
|
||||
}])
|
||||
35
src/data/structure/definitions/s1_dome_sq_cap.tres
Normal file
35
src/data/structure/definitions/s1_dome_sq_cap.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://d4hsi33r6tdla"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_fqsnc"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fqsnc")
|
||||
piece_name = "S 1 Dome Sq Cap"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 0.9396926207859084, -0.3420201433256687),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0.3420201433256687, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(-0.9396926207859084, 2.0865367673337435e-16, -0.3420201433256687),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.3420201433256687, 7.59437276011507e-17, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(-2.086536767333743e-16, -0.9396926207859084, -0.3420201433256687),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-7.594372760115069e-17, -0.3420201433256687, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(0.9396926207859084, -1.5649025755003072e-16, -0.3420201433256687),
|
||||
"position": Vector3(0.4999999999999999, -5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3420201433256687, -5.695779570086302e-17, 0.9396926207859084)
|
||||
}])
|
||||
30
src/data/structure/definitions/s1_dome_sq_side.tres
Normal file
30
src/data/structure/definitions/s1_dome_sq_side.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://e48yh6c4yj45"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_mh72h"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_mh72h")
|
||||
piece_name = "S 1 Dome Sq Side"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610964
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610964
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, 0, 0), Vector3(0.5, 0, 0), Vector3(0, -0.8660254037844386, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -0.9396926207859084, -0.3420201433256687),
|
||||
"position": Vector3(0, 0, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, -0.3420201433256687, 0.9396926207859084)
|
||||
}, {
|
||||
"normal": Vector3(-0.8528685319524433, 0.4924038765061041, -0.1736481776669304),
|
||||
"position": Vector3(0.25, -0.4330127018922193, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.1503837331804353, 0.08682408883346518, 0.984807753012208)
|
||||
}, {
|
||||
"normal": Vector3(0.8528685319524433, 0.4924038765061041, -0.1736481776669304),
|
||||
"position": Vector3(-0.25, -0.4330127018922193, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.1503837331804353, 0.08682408883346518, 0.984807753012208)
|
||||
}])
|
||||
35
src/data/structure/definitions/s1_flat_square.tres
Normal file
35
src/data/structure/definitions/s1_flat_square.tres
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://c00y87i8upmes"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_dyfmy"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_dyfmy")
|
||||
piece_name = "S 1 Flat Square"
|
||||
type = 1
|
||||
base_mass = 5.0
|
||||
cost = {
|
||||
"Aluminium": 5.0
|
||||
}
|
||||
shape = "Square"
|
||||
vertices = Array[Vector3]([Vector3(0.5, 0.5, 0), Vector3(-0.4999999999999999, 0.5, 0), Vector3(-0.5000000000000001, -0.4999999999999999, 0), Vector3(0.49999999999999983, -0.5000000000000001, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, 1, 0),
|
||||
"position": Vector3(5.551115123125783e-17, 0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1, 2.2204460492503136e-16, 0),
|
||||
"position": Vector3(-0.5, 5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-2.220446049250313e-16, -1, 0),
|
||||
"position": Vector3(-1.3877787807814457e-16, -0.5, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(1, -1.6653345369377348e-16, 0),
|
||||
"position": Vector3(0.4999999999999999, -5.551115123125783e-17, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
30
src/data/structure/definitions/s1_flat_triangle.tres
Normal file
30
src/data/structure/definitions/s1_flat_triangle.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://dn402fpt477ho"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_o42ak"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_o42ak")
|
||||
piece_name = "S 1 Flat Triangle"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610973
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610973
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1.1102230246251563e-16, -1, 0),
|
||||
"position": Vector3(0, -0.2886751345948128, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
30
src/data/structure/definitions/s2_equilateral_tri.tres
Normal file
30
src/data/structure/definitions/s2_equilateral_tri.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://byuhmmhixc2jp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_4ngxd"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_4ngxd")
|
||||
piece_name = "S 2 Equilateral Tri"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610973
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610973
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(0.5000000000000001, -0.28867513459481287, 0), Vector3(3.53525079574969e-17, 0.5773502691896258, 0), Vector3(-0.5000000000000001, -0.28867513459481275, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.8660254037844387, 0.5000000000000001, 0),
|
||||
"position": Vector3(0.25000000000000006, 0.1443375672974065, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-0.8660254037844386, 0.5000000000000001, 0),
|
||||
"position": Vector3(-0.25000000000000006, 0.14433756729740654, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}, {
|
||||
"normal": Vector3(-1.1102230246251563e-16, -1, 0),
|
||||
"position": Vector3(0, -0.2886751345948128, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, 0, 1)
|
||||
}])
|
||||
30
src/data/structure/definitions/s2_geo_tri.tres
Normal file
30
src/data/structure/definitions/s2_geo_tri.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://bx6w62uqleyro"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_lfiao"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_lfiao")
|
||||
piece_name = "S 2 Geo Tri"
|
||||
type = 1
|
||||
base_mass = 2.1650635094610964
|
||||
cost = {
|
||||
"Aluminium": 2.1650635094610964
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.5, -0.28867513459481287, 0), Vector3(0.5, -0.28867513459481287, 0), Vector3(0, 0.5773502691896257, 0)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0, -0.9342044743210295, -0.35673799931962513),
|
||||
"position": Vector3(0, -0.28867513459481287, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0, -0.35673799931962513, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
|
||||
"position": Vector3(0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
|
||||
}, {
|
||||
"normal": Vector3(-0.8090448070910988, 0.46710223716051485, -0.35673799931962524),
|
||||
"position": Vector3(-0.25, 0.14433756729740643, 0),
|
||||
"type": 0,
|
||||
"up": Vector3(-0.3089441699060312, 0.1783689996598126, 0.9342044743210295)
|
||||
}])
|
||||
30
src/data/structure/definitions/s2_geo_v2_a.tres
Normal file
30
src/data/structure/definitions/s2_geo_v2_a.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://b1kwhqprqqgpk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_mpy4c"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_mpy4c")
|
||||
piece_name = "S 2 Geo V 2 A"
|
||||
type = 1
|
||||
base_mass = 1.8234625713415449
|
||||
cost = {
|
||||
"Aluminium": 1.8234625713415449
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.4363389981249825, -0.03590512589028917, 0.21157661739748426), Vector3(0.3726779962499651, -0.38655593424232904, 0.14421169130125808), Vector3(0.06366100187501747, 0.42246106013261825, -0.35578830869874195)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(-0.3090169943749473, -0.8090169943749476, 0.5),
|
||||
"position": Vector3(-0.01967233145831576, -0.1305476470426356, 0.10994463378783126),
|
||||
"type": 0,
|
||||
"up": Vector3(0.25989191300775444, 0.4338885645526948, 0.8626684804161862)
|
||||
}, {
|
||||
"normal": Vector3(0.8506508083520399, 1.1102230246251565e-16, -0.5257311121191337),
|
||||
"position": Vector3(0.1348361657291579, 0.011095294085271323, -0.06538077038818868),
|
||||
"type": 0,
|
||||
"up": Vector3(0.42532540417602, 0.5877852522924731, 0.6881909602355868)
|
||||
}, {
|
||||
"normal": Vector3(-0.8090169943749475, 0.5000000000000001, -0.3090169943749475),
|
||||
"position": Vector3(-0.11516383427084212, 0.11945235295736434, -0.04456386339964247),
|
||||
"type": 0,
|
||||
"up": Vector3(0.16062203564002311, 0.6937804775604491, 0.702046444776163)
|
||||
}])
|
||||
30
src/data/structure/definitions/s2_geo_v2_b.tres
Normal file
30
src/data/structure/definitions/s2_geo_v2_b.tres
Normal file
@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="StructureData" load_steps=2 format=3 uid="uid://bwe7g4wf44opf"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdllldtl4bia3" path="res://data/structure/structure_data.gd" id="1_ra6qr"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_ra6qr")
|
||||
piece_name = "S 2 Geo V 2 B"
|
||||
type = 1
|
||||
base_mass = 2.165063509461097
|
||||
cost = {
|
||||
"Aluminium": 2.165063509461097
|
||||
}
|
||||
shape = "Triangle"
|
||||
vertices = Array[Vector3]([Vector3(-0.06366100187501737, -0.37267799624996495, 0.4363389981249825), Vector3(0.4363389981249825, -0.06366100187501737, -0.37267799624996495), Vector3(-0.37267799624996506, 0.43633899812498234, -0.06366100187501755)])
|
||||
mounts = Array[Dictionary]([{
|
||||
"normal": Vector3(0.5257311121191337, -0.8506508083520399, -1.1102230246251565e-16),
|
||||
"position": Vector3(0.11516383427084209, -0.13483616572915785, 0.019672331458315817),
|
||||
"type": 0,
|
||||
"up": Vector3(0.6881909602355868, 0.42532540417602005, 0.5877852522924731)
|
||||
}, {
|
||||
"normal": Vector3(-8.326672684688674e-17, 0.5257311121191337, -0.8506508083520399),
|
||||
"position": Vector3(0.019672331458315817, 0.11516383427084209, -0.1348361657291579),
|
||||
"type": 0,
|
||||
"up": Vector3(0.5877852522924731, 0.6881909602355867, 0.42532540417601994)
|
||||
}, {
|
||||
"normal": Vector3(-0.8506508083520399, -1.1102230246251565e-16, 0.5257311121191337),
|
||||
"position": Vector3(-0.1348361657291579, 0.019672331458315817, 0.11516383427084209),
|
||||
"type": 0,
|
||||
"up": Vector3(0.42532540417602, 0.5877852522924731, 0.6881909602355868)
|
||||
}])
|
||||
@ -8,6 +8,7 @@ enum PieceType {STRUT, PLATE, CONNECTOR}
|
||||
@export var type: PieceType = PieceType.STRUT
|
||||
@export var base_mass: float = 10.0
|
||||
@export var health_max: float = 100.0
|
||||
@export var cost: Dictionary = {"Aluminium": 10.0}
|
||||
|
||||
@export_group("Visuals & Physics")
|
||||
## The mesh to display for static pieces. Leave null for procedural pieces.
|
||||
|
||||
334
src/data/structure/structure_generator.gd
Normal file
334
src/data/structure/structure_generator.gd
Normal file
@ -0,0 +1,334 @@
|
||||
@tool
|
||||
class_name StructureGenerator extends Node
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
const DENSITY_ALUMINUM = 5.0 # kg per square meter (approx for hull plating)
|
||||
const COST_PER_KG = 1.0 # Currency per kg
|
||||
|
||||
|
||||
# Run this to regenerate the entire "System 1" library
|
||||
func generate_system_one():
|
||||
var dir = DirAccess.open("res://data/structure/definitions/")
|
||||
if not dir: DirAccess.make_dir_recursive_absolute("res://data/structure/definitions/")
|
||||
|
||||
print("--- Generating Design System 1: Geodesic ---")
|
||||
|
||||
# 1. Basic Flats (The backbone)
|
||||
create_polygon_plate("s1_flat_square", 4, 0.0)
|
||||
create_polygon_plate("s1_flat_triangle", 3, 0.0)
|
||||
|
||||
# 2. Cylinders (Corridors & Fuselage)
|
||||
# 8-Sided: ~2.6m Diameter (Good for corridors)
|
||||
create_cylinder_plate("s1_cyl8_wall", 8)
|
||||
# 12-Sided: ~3.9m Diameter (Good for main fuselage)
|
||||
create_cylinder_plate("s1_cyl12_wall", 12)
|
||||
|
||||
# 3. Square-Based Dome Cap
|
||||
# This creates a 'Cap' square that tilts its edges down,
|
||||
# and a 'Side' triangle that connects that square to a flatter ring below.
|
||||
create_square_dome_set("s1_dome_sq", 20.0) # 20 degree slope
|
||||
|
||||
print("Generation Complete.")
|
||||
|
||||
# --- CORE GENERATORS ---
|
||||
|
||||
# Creates a regular polygon (Square, Triangle, Hexagon, etc.)
|
||||
# bend_angle: Degrees to tilt the mount DOWN. 0 = Flat floor. >0 = Dome/Cylinder.
|
||||
func create_polygon_plate(id: String, sides: int, bend_angle: float):
|
||||
var res = _create_base_resource(id, "Plate")
|
||||
res.shape = "Square" if sides == 4 else "Triangle"
|
||||
|
||||
# Calculate Radius for exactly 1.0m Edge Length
|
||||
var radius = 0.5 / sin(PI / sides)
|
||||
var angle_step = TAU / sides
|
||||
var start_angle = PI / 4 if sides == 4 else -PI / 6 # Align flat edges to axes
|
||||
|
||||
res.vertices = [] as Array[Vector3]
|
||||
for i in range(sides):
|
||||
var theta = start_angle + i * angle_step
|
||||
res.vertices.append(Vector3(cos(theta), sin(theta), 0) * radius)
|
||||
|
||||
# Generate Mounts
|
||||
for i in range(sides):
|
||||
var p1 = res.vertices[i]
|
||||
var p2 = res.vertices[(i + 1) % sides]
|
||||
_add_edge_mount(res, p1, p2, bend_angle)
|
||||
|
||||
_finalize_resource(res, id)
|
||||
|
||||
# Creates a rectangular plate that forms one segment of a N-sided cylinder
|
||||
func create_cylinder_plate(id: String, total_sides: int):
|
||||
var res = _create_base_resource(id, "Cylinder Wall")
|
||||
res.shape = "Rect"
|
||||
|
||||
# Height = 1.0m (Standard grid)
|
||||
# Width = 1.0m (Chord length of the cylinder)
|
||||
var v0 = Vector3(-0.5, 0.5, 0)
|
||||
var v1 = Vector3(0.5, 0.5, 0)
|
||||
var v2 = Vector3(0.5, -0.5, 0)
|
||||
var v3 = Vector3(-0.5, -0.5, 0)
|
||||
res.vertices = [v0, v1, v2, v3] as Array[Vector3]
|
||||
|
||||
# Calculate the bend angle required to form a circle
|
||||
# Interior angle = (n-2)*180/n. Bend = (180 - Interior)/2 = 360/n / 2 = 180/n
|
||||
var bend = 180.0 / total_sides
|
||||
|
||||
# Top/Bottom: Flat (0 deg) to stack cylinders
|
||||
_add_edge_mount(res, v0, v1, 0.0)
|
||||
_add_edge_mount(res, v2, v3, 0.0)
|
||||
|
||||
# Left/Right: Bent to form the ring
|
||||
_add_edge_mount(res, v1, v2, bend)
|
||||
_add_edge_mount(res, v3, v0, bend)
|
||||
|
||||
_finalize_resource(res, id)
|
||||
|
||||
# Creates a Square Cap and its matching Triangle skirt
|
||||
func create_square_dome_set(prefix: String, slope_angle: float):
|
||||
# PART A: The Top Square
|
||||
# It acts like a flat square, but all mounts are tilted down by 'slope_angle'
|
||||
create_polygon_plate(prefix + "_cap", 4, slope_angle)
|
||||
|
||||
# PART B: The Side Triangle
|
||||
# This triangle connects the Tilted Square (Top) to a Flat Ring (Bottom)
|
||||
# It is an Isosceles triangle.
|
||||
# Top Edge: Matches the Square (1m).
|
||||
# Side Edges: Calculated to reach the flat plane.
|
||||
|
||||
var res = _create_base_resource(prefix + "_side", "Dome Tri")
|
||||
res.shape = "Triangle"
|
||||
|
||||
# We generate this triangle Flat on XY, but calculate mounts to fit the 3D gap.
|
||||
# Geometry:
|
||||
# The gap it fills has a top width of 1m.
|
||||
# The 'dihedral' angle between the Square and this Triangle is (180 - slope_angle).
|
||||
# To interface with the square, this triangle's Top Mount must act like it's bent "up" by slope_angle relative to the square's normal.
|
||||
|
||||
# Actually, simpler logic:
|
||||
# 1. Top Edge: Connects to the Square. Needs 'slope_angle' bend.
|
||||
# 2. Side Edges: Connect to neighbors in the ring.
|
||||
# 3. Bottom Vertex: Pointing down? No, usually a dome layer is a ring of triangles (point up) and triangles (point down).
|
||||
|
||||
# Let's assume a "Pyramid" style cap for simplicity first:
|
||||
# 4 Triangles meeting at a point is too sharp.
|
||||
# 4 Triangles connecting to a square creates a 'frustum'.
|
||||
|
||||
# Vertices for a standard 1m equilateral (placeholder for now, can be tweaked for specific radii)
|
||||
var h = sqrt(3) * 0.5
|
||||
var v0 = Vector3(-0.5, 0, 0)
|
||||
var v1 = Vector3(0.5, 0, 0)
|
||||
var v2 = Vector3(0, -h, 0)
|
||||
res.vertices = [v0, v1, v2] as Array[Vector3]
|
||||
|
||||
# Top Edge (v0->v1): Connects to Square.
|
||||
# The square is tilted down by 'slope'. To match it, we must tilt 'up' or 'down'?
|
||||
# Normals must oppose. Square normal is Tilted Down. This normal must be Tilted Down (relative to self) to be parallel?
|
||||
# Actually, both pieces tilt "in" towards the center of the sphere.
|
||||
_add_edge_mount(res, v0, v1, slope_angle)
|
||||
|
||||
# Side Edges (v1->v2, v2->v0): Connect to other triangles in the skirt.
|
||||
# These usually need a smaller bend angle, approx half the square's bend for a smooth transition.
|
||||
var side_bend = slope_angle * 0.5 # Approximation
|
||||
_add_edge_mount(res, v1, v2, side_bend)
|
||||
_add_edge_mount(res, v2, v0, side_bend)
|
||||
|
||||
_finalize_resource(res, prefix + "_side")
|
||||
|
||||
# --- HELPERS ---
|
||||
|
||||
func _create_base_resource(id: String, suffix: String) -> StructureData:
|
||||
var res = StructureData.new()
|
||||
res.piece_name = id.capitalize()
|
||||
res.type = StructureData.PieceType.PLATE
|
||||
return res
|
||||
|
||||
func _add_edge_mount(res: StructureData, p1: Vector3, p2: Vector3, bend_deg: float):
|
||||
var mid = (p1 + p2) / 2.0
|
||||
var edge_vector = (p2 - p1).normalized()
|
||||
# Flat normal points -Z (Back) or +Z depending on convention. Using BACK (+Z in Godot) as "Out"
|
||||
var flat_normal = edge_vector.cross(Vector3.BACK).normalized()
|
||||
|
||||
# Rotate normal "Down" around the edge
|
||||
var bend_rad = deg_to_rad(bend_deg)
|
||||
var final_normal = flat_normal.rotated(edge_vector, bend_rad)
|
||||
var final_up = Vector3.BACK.rotated(edge_vector, bend_rad)
|
||||
|
||||
res.add_mount(mid, final_normal, final_up)
|
||||
|
||||
func _finalize_resource(res: StructureData, filename: String):
|
||||
# Calculate Area for Mass/Cost
|
||||
var area = 0.0
|
||||
if res.vertices.size() >= 3:
|
||||
# Shoelace formula or simple triangle sum
|
||||
# For convex shapes centered on 0,0:
|
||||
for i in range(res.vertices.size()):
|
||||
var p1 = res.vertices[i]
|
||||
var p2 = res.vertices[(i + 1) % res.vertices.size()]
|
||||
area += 0.5 * (p1.cross(p2).length())
|
||||
|
||||
res.base_mass = area * DENSITY_ALUMINUM
|
||||
res.cost = {"Aluminium": res.base_mass * COST_PER_KG}
|
||||
|
||||
var path = "res://data/structure/definitions/%s.tres" % filename
|
||||
ResourceSaver.save(res, path)
|
||||
# print("Generated %s (Mass: %.1f kg)" % [filename, res.base_mass])
|
||||
|
||||
func generate_system_two_pentagonal():
|
||||
print("--- Generating Design System 2: Pentagonal ---")
|
||||
|
||||
# Configuration: 2m Radius Sphere
|
||||
# Icosahedron Edge Length (a) for radius (r): a = r / sin(72) * 2 approx...
|
||||
# Let's standardise on the edge length = 1.0m.
|
||||
# This results in a sphere radius of ~0.95m.
|
||||
var edge_length = 1.0
|
||||
|
||||
# 1. THE TUBE (Pentagonal Antiprism)
|
||||
# A tube made of 10 triangles per segment.
|
||||
# To fit a regular pentagon of side 1.0m.
|
||||
# Radius of pentagon = 1.0 / (2 * sin(36)) = ~0.85m
|
||||
|
||||
# We need a triangle that connects two points on the bottom pentagon
|
||||
# to one point on the top pentagon (rotated 36 degrees).
|
||||
# This forms an equilateral triangle if the height is correct (0.85m).
|
||||
create_polygon_plate("s2_equilateral_tri", 3, 0.0) # Standard 1m triangle
|
||||
|
||||
# 2. THE SPHERE CAP (Pentagonal Pyramid)
|
||||
# 5 of these triangles snap together to form a "Cap".
|
||||
# The "bend" angle is the dihedral angle of an Icosahedron ~138.19 deg.
|
||||
# Deviation from flat = (180 - 138.19) / 2 = ~20.9 degrees.
|
||||
|
||||
var bend_angle = 20.9
|
||||
|
||||
var res = _create_base_resource("s2_geo_tri", "Geo Plate")
|
||||
res.shape = "Triangle"
|
||||
res.vertices = _generate_equilateral_verts(1.0)
|
||||
|
||||
# Base Edge (0->1): Connects to the rest of the sphere (or extension ring)
|
||||
# Side Edges (1->2, 2->0): Connect to neighbors in the 5-way cluster
|
||||
|
||||
# All edges in a V1 sphere have the same bend angle!
|
||||
_add_edge_mount(res, res.vertices[0], res.vertices[1], bend_angle)
|
||||
_add_edge_mount(res, res.vertices[1], res.vertices[2], bend_angle)
|
||||
_add_edge_mount(res, res.vertices[2], res.vertices[0], bend_angle)
|
||||
|
||||
_finalize_resource(res, "s2_geo_tri")
|
||||
|
||||
print("System 2 Generated. Build tubes with 's2_equilateral_tri' and spheres with 's2_geo_tri'.")
|
||||
|
||||
func _generate_equilateral_verts(side: float) -> Array[Vector3]:
|
||||
var h = sqrt(3) * 0.5 * side
|
||||
return [
|
||||
Vector3(-side/2, -h/3, 0),
|
||||
Vector3(side/2, -h/3, 0),
|
||||
Vector3(0, 2*h/3, 0)
|
||||
]
|
||||
# src/data/structure/structure_generator.gd
|
||||
|
||||
func generate_system_two_v2_sphere():
|
||||
print("--- Generating Design System 2: V2 Geodesic (Room Size) ---")
|
||||
|
||||
# 1. Calculate Geometry (Normalized Radius = 1.0)
|
||||
var phi = (1.0 + sqrt(5.0)) / 2.0
|
||||
|
||||
# Icosahedron vertices
|
||||
var v0 = Vector3(0, 1, phi).normalized() # Pole
|
||||
var v4 = Vector3(1, phi, 0).normalized() # Neighbor
|
||||
var v8 = Vector3(phi, 0, 1).normalized() # Neighbor
|
||||
|
||||
# Subdivide for V2 (Midpoints projected to sphere)
|
||||
var v08 = (v0 + v8).normalized()
|
||||
var v84 = (v8 + v4).normalized()
|
||||
var v40 = (v4 + v0).normalized()
|
||||
|
||||
# We now have two distinct triangles:
|
||||
# Triangle A (Cap): v0 -> v08 -> v40
|
||||
# Triangle B (Face): v08 -> v84 -> v40
|
||||
|
||||
# 2. Scale Factor
|
||||
# We want the "Base" of Triangle A (edge v08-v40) to be exactly 1.0m.
|
||||
# This ensures it connects perfectly to our standard 1.0m Tubes.
|
||||
var unscaled_base_len = v08.distance_to(v40)
|
||||
var scale = 1.0 / unscaled_base_len
|
||||
|
||||
print("V2 Sphere Radius: %.2fm" % scale)
|
||||
|
||||
# 3. Generate Triangle A (The Pentagon Cap Piece)
|
||||
# This piece forms the 5-way corners.
|
||||
var res_a = _create_base_resource("s2_geo_v2_a", "Geo V2 Cap")
|
||||
res_a.shape = "Triangle"
|
||||
# Centering: Move vertices so the average is at (0,0,0)
|
||||
var center_a = (v0 + v08 + v40) / 3.0
|
||||
res_a.vertices = [
|
||||
(v0 - center_a) * scale, # Top (Pole)
|
||||
(v08 - center_a) * scale, # Right
|
||||
(v40 - center_a) * scale # Left
|
||||
] as Array[Vector3]
|
||||
|
||||
# Calculate exact bend angles based on the sphere normals
|
||||
# The mount normal should be the vertex normal (pointing out from sphere center)
|
||||
# relative to the flat face normal.
|
||||
_add_mount_from_sphere_geometry(res_a, v0, v08, v40, center_a)
|
||||
_finalize_resource(res_a, "s2_geo_v2_a")
|
||||
|
||||
# 4. Generate Triangle B (The Hexagon Face Piece)
|
||||
# This piece fills the gaps between caps.
|
||||
var res_b = _create_base_resource("s2_geo_v2_b", "Geo V2 Face")
|
||||
res_b.shape = "Triangle"
|
||||
var center_b = (v08 + v84 + v40) / 3.0
|
||||
res_b.vertices = [
|
||||
(v08 - center_b) * scale, # Top-Left
|
||||
(v84 - center_b) * scale, # Bottom
|
||||
(v40 - center_b) * scale # Top-Right
|
||||
] as Array[Vector3]
|
||||
|
||||
_add_mount_from_sphere_geometry(res_b, v08, v84, v40, center_b)
|
||||
_finalize_resource(res_b, "s2_geo_v2_b")
|
||||
|
||||
# Helper to calculate the correct mount angle for a spherical fragment
|
||||
func _add_mount_from_sphere_geometry(res: StructureData, p1_sphere: Vector3, p2_sphere: Vector3, p3_sphere: Vector3, center_sphere: Vector3):
|
||||
# We reconstruct the mounts for the 3 edges
|
||||
var points = [p1_sphere, p2_sphere, p3_sphere]
|
||||
|
||||
# Face Normal (Flat plate orientation)
|
||||
var face_normal = (p2_sphere - p1_sphere).cross(p3_sphere - p1_sphere).normalized()
|
||||
|
||||
for i in range(3):
|
||||
var a = points[i]
|
||||
var b = points[(i+1)%3]
|
||||
|
||||
# The mount position is the midpoint of the edge (relative to piece center)
|
||||
var mid_sphere = (a + b) / 2.0
|
||||
var mid_local = (mid_sphere - center_sphere) # Scale is applied later in the main loop, but directions don't care about scale
|
||||
|
||||
# The mount normal should point OUTWARD from the edge, but follow the sphere's curvature.
|
||||
# For a sphere, the perfect "Out" vector at the edge midpoint is just mid_sphere.normalized().
|
||||
# However, our mount system expects the normal to be roughly perpendicular to the edge.
|
||||
|
||||
var edge_vec = (b - a).normalized()
|
||||
# Vector perpendicular to edge, tangent to sphere surface at midpoint
|
||||
var sphere_tangent_out = edge_vec.cross(mid_sphere.normalized()).normalized()
|
||||
|
||||
# Wait, the mount normal needs to match the *other* piece's mount normal.
|
||||
# If both pieces are on the sphere, their mount normals should be parallel to the chord connecting them?
|
||||
# No, standard "snap" logic opposes normals.
|
||||
# If we use the Tangent, it points "along" the sphere surface.
|
||||
# When two pieces snap, they will form a continuous curve.
|
||||
|
||||
# Let's stick to the generated tangents.
|
||||
# We need to rotate this into the Local Space of the piece.
|
||||
# Actually, we are defining vertices in Local Space already.
|
||||
# But the normals calculated above are in Sphere Space.
|
||||
|
||||
# We need to rotate the calculated Sphere Normals into the Flat Face space?
|
||||
# No, StructureData mounts are defined in Local Space.
|
||||
# The Vertices in res.vertices are already (p - center).
|
||||
|
||||
# So:
|
||||
# Position: (mid_sphere - center_sphere) * scale (handled in main loop, we just need direction here)
|
||||
# Normal: sphere_tangent_out (It's a direction vector, translation doesn't affect it)
|
||||
# Up: The sphere normal at that point? (mid_sphere.normalized())
|
||||
|
||||
# Let's assume 'Up' is the surface normal (Out from center of sphere)
|
||||
var mount_up = mid_sphere.normalized()
|
||||
|
||||
res.add_mount((mid_sphere - center_sphere), sphere_tangent_out, mount_up)
|
||||
1
src/data/structure/structure_generator.gd.uid
Normal file
1
src/data/structure/structure_generator.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bkqqditmq34y3
|
||||
@ -36,6 +36,7 @@ properties/4/replication_mode = 0
|
||||
[node name="CharacterPawn3D" type="RigidBody3D"]
|
||||
physics_interpolation_mode = 1
|
||||
top_level = true
|
||||
mass = 80.0
|
||||
script = ExtResource("1_4frsu")
|
||||
metadata/_custom_type_script = "uid://cdmmiixa75f3x"
|
||||
|
||||
@ -76,7 +77,6 @@ metadata/_custom_type_script = "uid://y3vo40i16ek3"
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.13939085347041424, 0.5148942200402955)
|
||||
|
||||
[node name="PlayerController3d" parent="." instance=ExtResource("4_bcy3l")]
|
||||
mouse_sensitivity = null
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||
replication_config = SubResource("SceneReplicationConfig_gnddn")
|
||||
|
||||
@ -6,13 +6,13 @@ class_name EVAMovementComponent
|
||||
var pawn: CharacterPawn3D
|
||||
|
||||
## EVA Parameters (Moved from ZeroGPawn)
|
||||
@export var orientation_speed: float = 1.0 # Used for orienting body to camera
|
||||
@export var linear_acceleration: float = 1.0
|
||||
@export var roll_torque_acceleration: float = 0.25
|
||||
@export var orientation_speed: float = 25.0 # Used for orienting body to camera
|
||||
@export var linear_acceleration: float = 20.0
|
||||
@export var roll_torque_acceleration: float = 5.0
|
||||
@export var angular_damping: float = 0.95 # Base damping applied by pawn, suit might add more?
|
||||
@export var inertia_multiplier: float = 1.0 # How much the suit adds to pawn's base inertia (placeholder)
|
||||
@export var stabilization_kp: float = 5.0
|
||||
@export var stabilization_kd: float = 1.0
|
||||
@export var stabilization_kp: float = 25.0
|
||||
@export var stabilization_kd: float = 2 * sqrt(stabilization_kp)
|
||||
|
||||
var _auto_orient_target: Basis = Basis() # Stores the target orientation
|
||||
var _is_auto_orienting: bool = false # Flag to signal the pawn
|
||||
@ -91,8 +91,8 @@ func _process_auto_orientation(state: PhysicsDirectBodyState3D):
|
||||
_auto_orient_target,
|
||||
state.transform.basis,
|
||||
state.angular_velocity, # Read from state
|
||||
orientation_speed, # Kp
|
||||
2 * sqrt(orientation_speed) # Kd (Critically Damped)
|
||||
2 * sqrt(orientation_speed), # Kp (Critically Damped)
|
||||
orientation_speed # Kd
|
||||
)
|
||||
|
||||
# 2. Apply the torque to the physics state
|
||||
|
||||
@ -15,9 +15,19 @@ var build_mode_enabled: bool = false
|
||||
var is_snap_valid: bool = false # Track if we are currently snapped
|
||||
|
||||
# Resources
|
||||
const SQUARE_RES = preload("res://data/structure/definitions/1m_square_plate.tres")
|
||||
const TRIANGLE_RES = preload("res://data/structure/definitions/1m_triangle_plate.tres")
|
||||
const SQUARE_PIECES: Array[StructureData] = [
|
||||
preload("res://data/structure/definitions/1m_square_flat.tres"),
|
||||
preload("res://data/structure/definitions/1m_square_dome_top.tres"),
|
||||
]
|
||||
const TRIANGLE_PIECES: Array[StructureData] = [
|
||||
preload("res://data/structure/definitions/s2_equilateral_tri.tres"),
|
||||
preload("res://data/structure/definitions/s2_geo_tri.tres"),
|
||||
preload("res://data/structure/definitions/s2_geo_v2_a.tres"),
|
||||
preload("res://data/structure/definitions/s2_geo_v2_b.tres"),
|
||||
]
|
||||
const PROCEDURAL_PIECE_SCENE = preload("res://scenes/ship/builder/pieces/procedural_piece.tscn")
|
||||
var current_piece_index: int = 0
|
||||
var current_rotation_step: int = 0 # 0 to 3, representing 0, 90, 180, 270 degrees
|
||||
|
||||
class KeyInput:
|
||||
var pressed: bool = false
|
||||
@ -54,7 +64,8 @@ func _unhandled_input(event: InputEvent):
|
||||
_clear_preview()
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) # Ensure mouse captured when leaving
|
||||
else:
|
||||
_select_piece(1) # Default to square
|
||||
current_piece_index = 0
|
||||
_select_piece(SQUARE_PIECES[current_piece_index])
|
||||
print("Build Mode Enabled")
|
||||
|
||||
if not build_mode_enabled:
|
||||
@ -63,12 +74,19 @@ func _unhandled_input(event: InputEvent):
|
||||
return
|
||||
|
||||
# --- Build Mode Inputs ---
|
||||
# --- Piece Rotation (R key) ---
|
||||
if event is InputEventKey and event.pressed and event.keycode == KEY_R:
|
||||
current_rotation_step = (current_rotation_step + 1) % 4
|
||||
_update_preview_transform() # Update immediately
|
||||
|
||||
# --- Piece Cycling (Brackets [ ]) ---
|
||||
if event is InputEventKey and event.pressed:
|
||||
if event.keycode == KEY_1:
|
||||
print("Selected Square Piece")
|
||||
_select_piece(1)
|
||||
current_piece_index = (current_piece_index - 1 + SQUARE_PIECES.size()) % SQUARE_PIECES.size()
|
||||
_select_piece(SQUARE_PIECES[current_piece_index])
|
||||
elif event.keycode == KEY_2:
|
||||
_select_piece(2)
|
||||
current_piece_index = (current_piece_index + 1) % TRIANGLE_PIECES.size() % TRIANGLE_PIECES.size()
|
||||
_select_piece(TRIANGLE_PIECES[current_piece_index])
|
||||
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
@ -122,14 +140,10 @@ func _physics_process(_delta):
|
||||
|
||||
# --- Builder Functions ---
|
||||
|
||||
func _select_piece(index: int):
|
||||
func _select_piece(piece_data: StructureData):
|
||||
_clear_preview()
|
||||
|
||||
if index == 1:
|
||||
active_structure_data = SQUARE_RES
|
||||
elif index == 2:
|
||||
active_structure_data = TRIANGLE_RES
|
||||
|
||||
active_structure_data = piece_data
|
||||
print("Selected piece for building:", piece_data.piece_name)
|
||||
if active_structure_data:
|
||||
var piece = PROCEDURAL_PIECE_SCENE.instantiate()
|
||||
piece.structure_data = active_structure_data
|
||||
@ -159,7 +173,6 @@ func _update_preview_transform():
|
||||
var target_module: Module = null
|
||||
if collider is PieceMount:
|
||||
# If we hit a mount, get its piece and its module, get its module
|
||||
print(collider.get_parent())
|
||||
if collider.get_parent() is StructuralPiece:
|
||||
target_module = collider.get_parent().get_parent()
|
||||
if collider.owner is Module: target_module = collider.owner
|
||||
@ -172,7 +185,7 @@ func _update_preview_transform():
|
||||
target_module,
|
||||
hit_pos, # Ray hit position
|
||||
)
|
||||
print(snap_transform)
|
||||
|
||||
# If the transform has changed significantly from the hit pos, it means a snap occurred.
|
||||
# (Simple heuristic: check distance from hit to new origin)
|
||||
if snap_transform.origin.distance_to(hit_pos) < SnappingTool.SNAP_DISTANCE:
|
||||
@ -220,7 +233,7 @@ func server_request_place_piece(resource_path: String, transform: Transform3D):
|
||||
|
||||
# Find nearby module to attach to
|
||||
var query_pos = transform.origin
|
||||
var module = _find_module_near_server(query_pos)
|
||||
var module: Module = _find_module_near_server(query_pos)
|
||||
|
||||
if not module:
|
||||
# Logic to create a new module could go here
|
||||
@ -229,11 +242,12 @@ func server_request_place_piece(resource_path: String, transform: Transform3D):
|
||||
new_module.name = "Module_%s" % get_process_delta_time() # Unique name
|
||||
possessed_pawn.get_parent().add_child(new_module)
|
||||
new_module.global_position = query_pos
|
||||
new_module.physics_mode = OrbitalBody3D.PhysicsMode.COMPOSITE
|
||||
module = new_module
|
||||
print("Created new module %s for piece placement." % new_module.name)
|
||||
|
||||
if module:
|
||||
var piece = PROCEDURAL_PIECE_SCENE.instantiate()
|
||||
var piece: ProceduralPiece = PROCEDURAL_PIECE_SCENE.instantiate()
|
||||
piece.structure_data = res
|
||||
module.add_child(piece)
|
||||
piece.global_transform = transform
|
||||
@ -241,6 +255,8 @@ func server_request_place_piece(resource_path: String, transform: Transform3D):
|
||||
|
||||
# Trigger weld logic on the new piece
|
||||
piece.try_weld()
|
||||
module.recalculate_physical_properties()
|
||||
print("Placed piece %s on module %s" % [piece.name, module])
|
||||
|
||||
# Helper to find modules on server side (uses global overlap check)
|
||||
func _find_module_near_server(pos: Vector3) -> Module:
|
||||
|
||||
@ -5,6 +5,26 @@ class_name Module extends OrbitalBody3D
|
||||
|
||||
const COMPONENT_GRID_SIZE = 64.0
|
||||
|
||||
func _ready():
|
||||
super._ready()
|
||||
|
||||
child_entered_tree.connect(_on_child_entered_tree)
|
||||
|
||||
# Handle existing children (if any existed before ready)
|
||||
for child in get_children():
|
||||
_enforce_child_physics_mode(child)
|
||||
|
||||
func _on_child_entered_tree(node: Node):
|
||||
_enforce_child_physics_mode(node)
|
||||
|
||||
func _enforce_child_physics_mode(node: Node):
|
||||
# Only affect OrbitalBody3D children that are NOT the module itself
|
||||
if node is OrbitalBody3D and node != self:
|
||||
# We only enforce ANCHORED if this Module is a COMPOSITE (Root)
|
||||
if physics_mode == PhysicsMode.COMPOSITE:
|
||||
node.physics_mode = PhysicsMode.ANCHORED
|
||||
# print("Module '%s' enforced ANCHORED mode on child '%s'" % [name, node.name])
|
||||
|
||||
# --- Helper functions to get children by type ---
|
||||
func get_structural_pieces() -> Array[StructuralPiece]:
|
||||
var pieces: Array[StructuralPiece]
|
||||
|
||||
@ -3,6 +3,8 @@ class_name ProceduralPiece extends StructuralPiece
|
||||
@export var vertices: Array[Vector3] = []
|
||||
@export var thickness: float = 0.02
|
||||
|
||||
const PLATE_SHADER = preload("res://scenes/ship/builder/pieces/structural_plate.gdshader")
|
||||
|
||||
# This is inherited from StructuralPiece, but we use the setter to update visuals
|
||||
# var is_preview: bool (Inherited)
|
||||
|
||||
@ -100,81 +102,75 @@ func _build_strut_mesh():
|
||||
col.rotation = mesh_inst.rotation
|
||||
add_child(col)
|
||||
|
||||
|
||||
func _build_plate_mesh():
|
||||
var st = SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
|
||||
# We assume the vertices are on the XY plane (Z=0) for simplicity of extrusion logic.
|
||||
# If your vertices have varying Z, you'd need to calculate the face normal first.
|
||||
# For plates defined in the resource, Z is 0.
|
||||
|
||||
var half_thick = thickness / 2.0
|
||||
var top_verts = []
|
||||
var bottom_verts = []
|
||||
|
||||
# Generate offset vertices
|
||||
# Assuming "Forward" / Thickness direction is -Z (Standard Godot Back)
|
||||
# Actually, let's align thickness along Local Z axis.
|
||||
# UV Calculation Helpers
|
||||
var min_v = Vector2(INF, INF)
|
||||
var max_v = Vector2(-INF, -INF)
|
||||
for v in vertices:
|
||||
top_verts.append(Vector3(v.x, v.y, half_thick)) # Front Face (+Z)
|
||||
bottom_verts.append(Vector3(v.x, v.y, -half_thick)) # Back Face (-Z)
|
||||
|
||||
# --- 1. Top Face (Front) ---
|
||||
var normal_top = Vector3.BACK # +Z in Godot is Back, but usually front face points to +Z
|
||||
# Wait, Godot coordinate system: -Z is Forward.
|
||||
# Let's say +Z is "Top" of the plate relative to its local space.
|
||||
min_v.x = min(min_v.x, v.x)
|
||||
min_v.y = min(min_v.y, v.y)
|
||||
max_v.x = max(max_v.x, v.x)
|
||||
max_v.y = max(max_v.y, v.y)
|
||||
var size_v = max_v - min_v
|
||||
|
||||
# Fan Triangulation for Convex Shape
|
||||
for v in vertices:
|
||||
top_verts.append(Vector3(v.x, v.y, half_thick))
|
||||
bottom_verts.append(Vector3(v.x, v.y, -half_thick))
|
||||
|
||||
# --- 1. Top Face (Inside - Red Channel) ---
|
||||
st.set_color(Color(1, 0, 0, 1))
|
||||
st.set_normal(Vector3.BACK)
|
||||
|
||||
# Fan Triangulation
|
||||
for i in range(1, top_verts.size() - 1):
|
||||
st.set_normal(Vector3.BACK) # +Z
|
||||
st.add_vertex(top_verts[0])
|
||||
st.add_vertex(top_verts[i])
|
||||
st.add_vertex(top_verts[i+1])
|
||||
_add_vertex_with_uv(st, top_verts[0], min_v, size_v)
|
||||
_add_vertex_with_uv(st, top_verts[i], min_v, size_v)
|
||||
_add_vertex_with_uv(st, top_verts[i+1], min_v, size_v)
|
||||
|
||||
# --- 2. Bottom Face (Back) ---
|
||||
# --- 2. Bottom Face (Outside - Blue Channel) ---
|
||||
st.set_color(Color(0, 0, 1, 1))
|
||||
st.set_normal(Vector3.FORWARD)
|
||||
|
||||
for i in range(1, bottom_verts.size() - 1):
|
||||
st.set_normal(Vector3.FORWARD) # -Z
|
||||
# Winding order reversed for back face
|
||||
st.add_vertex(bottom_verts[0])
|
||||
st.add_vertex(bottom_verts[i+1])
|
||||
st.add_vertex(bottom_verts[i])
|
||||
_add_vertex_with_uv(st, bottom_verts[0], min_v, size_v)
|
||||
_add_vertex_with_uv(st, bottom_verts[i+1], min_v, size_v)
|
||||
_add_vertex_with_uv(st, bottom_verts[i], min_v, size_v)
|
||||
|
||||
# --- 3. Side Faces ---
|
||||
# Connect Top[i] to Bottom[i] around the perimeter
|
||||
# --- 3. Side Faces (Sides - Black) ---
|
||||
st.set_color(Color(0, 0, 0, 1))
|
||||
|
||||
for i in range(vertices.size()):
|
||||
var next_i = (i + 1) % vertices.size()
|
||||
|
||||
var v1 = top_verts[i]
|
||||
var v2 = top_verts[next_i]
|
||||
var v3 = bottom_verts[next_i]
|
||||
var v4 = bottom_verts[i]
|
||||
|
||||
# Calculate side normal
|
||||
var side_vec = (v2 - v1).normalized()
|
||||
var up_vec = Vector3.BACK # +Z
|
||||
var normal = side_vec.cross(up_vec).normalized() # Points outward
|
||||
|
||||
var normal = side_vec.cross(Vector3.BACK).normalized()
|
||||
st.set_normal(normal)
|
||||
|
||||
# Quad = 2 Triangles
|
||||
# Tri 1: v1, v2, v4
|
||||
st.add_vertex(v1)
|
||||
st.add_vertex(v2)
|
||||
st.add_vertex(v4)
|
||||
|
||||
# Tri 2: v2, v3, v4
|
||||
st.add_vertex(v2)
|
||||
st.add_vertex(v3)
|
||||
st.add_vertex(v4)
|
||||
# Sides don't need specific UVs for the margin shader
|
||||
st.set_uv(Vector2.ZERO)
|
||||
st.add_vertex(v1); st.add_vertex(v2); st.add_vertex(v4)
|
||||
st.add_vertex(v2); st.add_vertex(v3); st.add_vertex(v4)
|
||||
|
||||
var mesh_inst = MeshInstance3D.new()
|
||||
mesh_inst.mesh = st.commit()
|
||||
|
||||
# Apply Blueprint Material if Preview
|
||||
_apply_material(mesh_inst)
|
||||
|
||||
add_child(mesh_inst)
|
||||
|
||||
# Generate Labels
|
||||
_generate_serial_labels(half_thick)
|
||||
|
||||
# Collision (Convex)
|
||||
# For collision, we just feed ALL the points (top and bottom) to the convex hull generator.
|
||||
var col = CollisionShape3D.new()
|
||||
@ -185,9 +181,19 @@ func _build_plate_mesh():
|
||||
col.shape = shape
|
||||
add_child(col)
|
||||
|
||||
|
||||
func _add_vertex_with_uv(st: SurfaceTool, v: Vector3, min_v: Vector2, size_v: Vector2):
|
||||
var uv = Vector2(
|
||||
(v.x - min_v.x) / size_v.x,
|
||||
(v.y - min_v.y) / size_v.y
|
||||
)
|
||||
st.set_uv(uv)
|
||||
st.add_vertex(v)
|
||||
|
||||
|
||||
func _apply_material(mesh_inst: MeshInstance3D):
|
||||
var mat = StandardMaterial3D.new()
|
||||
if is_preview:
|
||||
var mat = StandardMaterial3D.new()
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.albedo_color = Color(0.2, 0.6, 1.0, 0.4) # Semi-transparent Blue
|
||||
mat.emission_enabled = true
|
||||
@ -195,15 +201,41 @@ func _apply_material(mesh_inst: MeshInstance3D):
|
||||
mat.emission_energy_multiplier = 1.0
|
||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED # See inside
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED # Glowy
|
||||
# super._apply_material(mesh_inst) # Use simple transparent blue
|
||||
mesh_inst.material_override = mat
|
||||
else:
|
||||
mat.albedo_color = Color(0.5, 0.5, 0.5) # Concrete/Metal Grey
|
||||
mat.metallic = 0.5
|
||||
mat.roughness = 0.5
|
||||
|
||||
mesh_inst.material_override = mat
|
||||
var mat = ShaderMaterial.new()
|
||||
# Use Shader Material
|
||||
mat.shader = PLATE_SHADER
|
||||
|
||||
mesh_inst.material_override = mat
|
||||
|
||||
func _generate_serial_labels(offset_z: float):
|
||||
# Generate a mock serial based on instance ID or data
|
||||
var serial_text = "IND-%d\nMDL-%s" % [get_instance_id() % 10000, structure_data.shape.left(3).to_upper()]
|
||||
|
||||
# Top Label (Inside)
|
||||
var label_in = Label3D.new()
|
||||
label_in.text = serial_text
|
||||
label_in.font_size = 12
|
||||
label_in.position = Vector3(0, 0, offset_z + 0.005) # Slight offset to avoid Z-fighting
|
||||
label_in.pixel_size = 0.002
|
||||
label_in.modulate = Color(0.5, 0.5, 0.5, 0.8)
|
||||
label_in.rotation_degrees = Vector3(0, 0, 0)
|
||||
add_child(label_in)
|
||||
|
||||
# Bottom Label (Outside)
|
||||
var label_out = Label3D.new()
|
||||
label_out.text = serial_text
|
||||
label_out.font_size = 12
|
||||
label_out.position = Vector3(0, 0, -offset_z - 0.005)
|
||||
label_out.pixel_size = 0.002
|
||||
label_out.modulate = Color(0.3, 0.3, 0.3, 0.8)
|
||||
label_out.rotation_degrees = Vector3(180, 0, 0) # Face down
|
||||
add_child(label_out)
|
||||
|
||||
# Override the parent's update function to use our specific material logic
|
||||
func _update_preview_visuals():
|
||||
for child in get_children():
|
||||
if child is MeshInstance3D:
|
||||
_apply_material(child)
|
||||
_apply_material(child)
|
||||
|
||||
@ -126,4 +126,4 @@ func _update_preview_visuals():
|
||||
func _exit_tree():
|
||||
for neighbor in connected_neighbors:
|
||||
if is_instance_valid(neighbor):
|
||||
neighbor.connected_neighbors.erase(self)
|
||||
neighbor.connected_neighbors.erase(self)
|
||||
|
||||
66
src/scenes/ship/builder/pieces/structural_plate.gdshader
Normal file
66
src/scenes/ship/builder/pieces/structural_plate.gdshader
Normal file
@ -0,0 +1,66 @@
|
||||
shader_type spatial;
|
||||
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx;
|
||||
|
||||
// Configuration
|
||||
uniform vec4 margin_color : source_color = vec4(0.6, 0.6, 0.6, 1.0);
|
||||
uniform vec4 inside_color : source_color = vec4(0.7, 0.2, 0.2, 1.0); // Light Red
|
||||
uniform vec4 outside_color : source_color = vec4(0.3, 0.3, 0.35, 1.0); // Grey-ish
|
||||
|
||||
uniform float margin_width : hint_range(0.0, 0.5) = 0.05;
|
||||
uniform float ridge_frequency = 10.0;
|
||||
|
||||
void fragment() {
|
||||
// COLOR passed from mesh generation:
|
||||
// R=1 (Inside), B=1 (Outside), Black (Sides)
|
||||
float is_inside = COLOR.r;
|
||||
float is_outside = COLOR.b;
|
||||
float is_side = 1.0 - is_inside - is_outside;
|
||||
|
||||
// --- MARGIN CALCULATION ---
|
||||
// UVs are assumed to be centered at 0.5, 0.5.
|
||||
// Distance from center > (0.5 - margin) means we are at the edge.
|
||||
vec2 centered_uv = abs(UV - 0.5);
|
||||
float dist_from_center = max(centered_uv.x, centered_uv.y);
|
||||
float in_margin = step(0.5 - margin_width, dist_from_center);
|
||||
|
||||
// --- TEXTURES ---
|
||||
|
||||
// 1. Inside (Friction Mat)
|
||||
vec3 inside_albedo = inside_color.rgb;
|
||||
float inside_roughness = 0.8;
|
||||
float inside_metallic = 0.1;
|
||||
|
||||
// 2. Outside (Reinforced Hull)
|
||||
// Simple procedural grid ridges
|
||||
float ridges_x = step(0.9, fract(UV.x * ridge_frequency));
|
||||
float ridges_y = step(0.9, fract(UV.y * ridge_frequency));
|
||||
float ridges = max(ridges_x, ridges_y);
|
||||
|
||||
vec3 outside_albedo = mix(outside_color.rgb, outside_color.rgb * 0.8, ridges);
|
||||
float outside_roughness = 0.6;
|
||||
float outside_metallic = 0.5;
|
||||
|
||||
// 3. Margin / Sides (Exposed Metal)
|
||||
vec3 metal_albedo = margin_color.rgb;
|
||||
float metal_roughness = 0.3;
|
||||
float metal_metallic = 1.0;
|
||||
|
||||
// --- MIXING ---
|
||||
// If we are on a Side OR in the Margin of a face, use Metal.
|
||||
float show_metal = max(is_side, in_margin);
|
||||
|
||||
vec3 face_albedo = mix(outside_albedo, inside_albedo, is_inside);
|
||||
float face_roughness = mix(outside_roughness, inside_roughness, is_inside);
|
||||
float face_metallic = mix(outside_metallic, inside_metallic, is_inside);
|
||||
|
||||
ALBEDO = mix(face_albedo, metal_albedo, show_metal);
|
||||
METALLIC = mix(face_metallic, metal_metallic, show_metal);
|
||||
ROUGHNESS = mix(face_roughness, metal_roughness, show_metal);
|
||||
|
||||
// Normal Map for Ridges (Optional fake bump)
|
||||
if (is_outside > 0.5 && show_metal < 0.5) {
|
||||
NORMAL_MAP_DEPTH = 1.0;
|
||||
// Simple normal perturbation based on ridges
|
||||
// ( omitted for brevity, flat normal usually fine for low poly style )
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
uid://1fn7j0a2cl4s
|
||||
@ -67,6 +67,7 @@ shape = SubResource("BoxShape3D_25xtv")
|
||||
transform = Transform3D(1.91069e-15, 4.37114e-08, 1, 1, -4.37114e-08, 0, 4.37114e-08, 1, -4.37114e-08, 0, 0, 25)
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D5"]
|
||||
transform = Transform3D(0.9999999999999998, 0, 0, 0, 0.9999999999999996, 0, -7.888609052210118e-31, 0, 0.9999999999999998, 1.1338752239407265e-09, 0.02594011577424382, 0.03135385943891754)
|
||||
mesh = SubResource("BoxMesh_kateb")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D5"]
|
||||
@ -98,7 +99,7 @@ shape = SubResource("CylinderShape3D_nvgim")
|
||||
transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, 0, 0, 0)
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D8"]
|
||||
transform = Transform3D(0.9999999999999997, 0, 0, 0, 0.9999999999999997, 0, 0, 0, 1, 0.09530011764134061, 0.10799497165389675, -0.025631436817796976)
|
||||
transform = Transform3D(0.9999999999999994, 0, 0, 0, 0.9999999999999994, 0, 0, 0, 1, 0.07426028835969876, 0.10219171647182022, -0.0326458423771003)
|
||||
mesh = SubResource("CylinderMesh_nvgim")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D8"]
|
||||
|
||||
@ -109,9 +109,10 @@ func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||
accumulated_force = Vector3.ZERO
|
||||
accumulated_torque = Vector3.ZERO
|
||||
|
||||
|
||||
# --- PHYSICAL PROPERTIES RECALCULATION FOR COMPOSITE BODIES ---
|
||||
func recalculate_physical_properties():
|
||||
if physics_mode != PhysicsMode.COMPOSITE:
|
||||
print("Recalculating physical properties for non-COMPOSITE body:", self)
|
||||
mass = base_mass
|
||||
if inertia == Vector3.ZERO:
|
||||
inertia = Vector3(1.0, 1.0, 1.0)
|
||||
@ -126,11 +127,12 @@ func recalculate_physical_properties():
|
||||
return
|
||||
|
||||
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
|
||||
var total_mass = 0.0
|
||||
# REFACTOR: Use Vector3
|
||||
var weighted_local_pos_sum = Vector3.ZERO
|
||||
var total_mass: float = 0.0
|
||||
var weighted_local_pos_sum: Vector3 = Vector3.ZERO
|
||||
|
||||
for part in all_parts:
|
||||
total_mass += part.base_mass
|
||||
# Calculate position relative to the Module root
|
||||
var local_pos = part.global_position - self.global_position
|
||||
weighted_local_pos_sum += local_pos * part.base_mass
|
||||
|
||||
@ -139,18 +141,36 @@ func recalculate_physical_properties():
|
||||
local_center_of_mass = weighted_local_pos_sum / total_mass
|
||||
|
||||
# --- Step 2: Calculate Total Moment of Inertia around the LOCAL CoM ---
|
||||
var total_inertia: Vector3 = Vector3.ZERO
|
||||
# using the Parallel Axis Theorem for Point Masses: I = sum( m * r^2 )
|
||||
# where r is the perpendicular distance to the axis of rotation.
|
||||
var total_inertia = Vector3.ZERO
|
||||
|
||||
for part in all_parts:
|
||||
var local_pos = part.global_position - self.global_position
|
||||
# REFACTOR: This logic (Parallel Axis Theorem) is still correct for Vector3
|
||||
var r_squared = (local_pos - local_center_of_mass).length_squared()
|
||||
# total_inertia += part.base_mass * r_squared
|
||||
var r_vec = local_pos - local_center_of_mass # Vector from CoM to part
|
||||
|
||||
# Distance squared to the X, Y, and Z axes respectively:
|
||||
var r_sq_x = r_vec.y * r_vec.y + r_vec.z * r_vec.z
|
||||
var r_sq_y = r_vec.x * r_vec.x + r_vec.z * r_vec.z
|
||||
var r_sq_z = r_vec.x * r_vec.x + r_vec.y * r_vec.y
|
||||
|
||||
total_inertia.x += part.base_mass * r_sq_x
|
||||
total_inertia.y += part.base_mass * r_sq_y
|
||||
total_inertia.z += part.base_mass * r_sq_z
|
||||
|
||||
# --- Step 3: Assign the final values ---
|
||||
self.mass = total_mass
|
||||
self.inertia = total_inertia * 0.01
|
||||
if self.inertia == Vector3.ZERO: # Safety check
|
||||
inertia = Vector3(1.0, 1.0, 1.0)
|
||||
|
||||
# Explicitly tell the physics engine where the new center of mass is
|
||||
self.center_of_mass_mode = RigidBody3D.CENTER_OF_MASS_MODE_CUSTOM
|
||||
self.center_of_mass = local_center_of_mass
|
||||
|
||||
# Enforce a minimum inertia to prevent physics explosions
|
||||
if total_inertia.length_squared() < 0.1:
|
||||
total_inertia = Vector3(1.0, 1.0, 1.0)
|
||||
|
||||
self.inertia = total_inertia
|
||||
print("Recalculated Ship: Mass %.1f, COM %s, Inertia %s" % [mass, center_of_mass, inertia])
|
||||
|
||||
# A recursive helper function to get an array of all OrbitalBody3D children
|
||||
func _collect_anchored_parts(parts_array: Array):
|
||||
|
||||
@ -31,7 +31,7 @@ func _process(_delta):
|
||||
if not multiplayer.is_server(): return
|
||||
|
||||
# Safety check: don't try to spawn if the system isn't ready or is deleted
|
||||
if not is_instance_valid(current_star_system): return
|
||||
# if not is_instance_valid(current_star_system): return
|
||||
|
||||
if not waiting_players.is_empty() and find_available_spawner():
|
||||
_try_spawn_waiting_player()
|
||||
|
||||
Reference in New Issue
Block a user