ShipScaper is going to be a program where you can create the shape of ship (with ability to specify engines, weapons etc if time allows) on a hex grid and it will generate a 3d model based on that. The plan is to figure out a good way to separate the given ship into components, assign human-generated models to those components, and thus combine them to create the shape of the ship. Components should be combined by something similar to an L-system, meaning it should do stuff like 4-way symmetry, wings that curve downwards and so on dynamically, instead of staying as a flat bunch of components like the underlying hex grid.
I will be making it in Unreal 4, and hopefully I will be able to dedicate the last or last couple of milestones to improving the visuals.
Milestone 1 (25.09)
- Refamiliarize myself with Unreal 4
- Create interactive hex grid
This milestone was spent setting up source control and reminding myself of the fundamentals of getting stuff working in Unreal and also relearning Blueprints and the material editor.
I created a plane with a hex material. The material only displays the hex pattern near a specified location. I also set up the blueprints so the plane detects the location of the cursor and only displays the hex pattern near the cursor location. It also responds to clicks and understand which hex was clicked. For the purposes of visualization it displays a debug sphere at the hex.
Milestone 2 (9.10)
- Allow selection/deselection of filled hexes
- Pass list of selected hexes to a C++ function for processing, get back list of components
- Display components
The hex grid now tracks and displays what locations have been selected. There is also now a C++ function that usable in blueprints which takes in the coordinates of the selected hexes and combines them into components and presently returns just the component id of each hex. The id of the component is shown on selected hexes and also all share the same color.
The method for combining the hexes at the moment is as follows:
- Select random hex
- Go through a list of component shapes
- Select first component which matches available hexes
- Continue until all hexes have been assigned
Milestone 3 (23.10)
- Improve component assignment logic
- Assign 3d models to components
- Add blueprint methods for defining new components and their shapes
Ship parts are now defined as Actors, with all the associated possibilities like attached particle systems, events and so on. Each part has 3 sets associated with it (variables with default values), which define where the part may be placed. This data and classes are given to a C++ function, which returns a map of hex locations and classes. The returned mapping is used to spawn all the actors which compose the ship.
Adding a new part is therefore as simple as defining a new actor (inheriting from BasePart), changing the default values of the sets defining the shape, and adding any components you wish. It also needs to be added to the list of parts in the HexGrid actor.
Milestone 4 (06.11)
- Support for part prioritization
- Support symmetry (parts and part assignment both)
- Begin work on graph of connected parts
Placement of parts is now done in several passes, and parts within those passes have different priorities which sets the order of testing for fitting parts. For example placement of the large circular part in the image is done in the first pass, which means it tests every possible location before moving forward to the next pass with smaller parts. This ensures that smaller parts don't get in the way of placing bigger ones.
The sequence of placement goes:
- Select parts for pass
- Select random chosen hex
- Iterate through selected parts in order of priority to see if one fits, place if possible
- Repeat hex selection and part placement until all hexes tested
- Repeat part selection and start new pass until all passes have been done
Both the pass and priority are numeric values associated with a part.
Symmetry and mirroring on a part level is supported by mirroring all sets of associated hexes and setting the scale on the x axis of the actor to -1.0. Mirroring on the x axis is calculated with (x,y)->(-x,y-x). In the code these mirrored parts are simply appended to the list of parts, and even exist for parts which are already symmetrical.
Symmetry on the ship level is done by first selecting the symmetric portion of active hexes and doing placement exclusively on that portion. Part placement requirements still account for the original set. After that the rest of the ship is filled in. This system is not perfect as it does not comprehend when a large portion of a ship is not supposed to be symmetrical and still tries to match parts, but it currently functions fine.
I also did some cleanup to cut down the size of the packaged project. The link to the build is at the top of this page.
The design of the graph of connected parts is purely theoretical at the moment. The basic principle is the parts generated in the previous stage are combined into larger components according to their connections away from the central line of the ship. They are connected on the basis of having to be rotated or split at the same time (eg they share children and x coordinates). Rotation and splitting will be done parallel to the central line of the ship. The image above shows how a ship should be subdivided.
Milestone 5 (20.11)
- Implement part graph and L-system based on graph
- Check microphone
Implementation ended up being a lot more complicated than expected, meaning no work was done on the L-system.
Parts are now combined into "chunks" where all parts are given the same transform. This is done based on their connections. All adjacent parts are considered to be connected. Chunks are created by combining connected parts along the y axis.
Afterwards all neighbours leading towards the center line are found. These neighbours are considered to be parents. These parents are then combined in the following manner until all chunks only have a single parent:
- All parents sharing x coordinates with a child are combined with the child
- When there is more than 1 parent, the parents are combined
The end result is a tree, and the final location of each ship part is calculated by combining the transforms of each chunk up to the root. At the moment this does not work as the transforms are wrong and the locations of the parts are calculated in world space.
Milestone 6 (4.12)
- Fix transforms
- L-system for smarter randomization
- More content
- Alternative camera mode
Transforms are now properly handled. The method for this is that the "centre" of each chunk is actually the outer edge hex of the parent chunk. This makes the part rotate around the hexes it is attached to. Each part is therefore compared in worldspace to the location of its chunk to bring it to chunk space, and then multiplied with the transforms of each chunk higher in the hierarchy.
The current L-system is presently very simplistic, with only the initial node doing anything other than rotations. The initial node can replicate attached chunks into up to 3, and gives all subsequent nodes a rotation direction (can be straight).
It is now possible to switch camera modes by pressing tab, to switch between an overhead camera and a orbital camera. The orbital camera is controlled with the mouse, and the overhead camera can now be zoomed in and out with the scroll wheel and moved with wasd keys.