Groups
All objects are located within a 3D group, made of 1x1x1 metres cell
When the first object of a group is placed, that defines the group's root position & rotation. For all intents and purposes: 1 group = 1 root = 1 Vector3 position. There might be an option later to destroy objects which could mean moving the root, but that's not even necessary (a root position does not really need to be inside one of the group's objects)
Every group has a parent empty GameObject, I can't attach the BeyondGroup component to it because it's not a MonoBehaviour.
Any object has a 3D Integer position relative to the group's root, so we know how to snap them in place. That's the key: the position is made of integers, not floats, which is what we need to snap (not colliders or anchors or features or other nonsense)
Group rotation: a group has a specific rotation defined by the rotation of its first object, when the root was created. It should not ever be possible to change it (if you build your house facing the wrong way, you can't move your house later). Conclusion : if an object can be rotated, it's not part of a group, it's a moveable object.
Constraints
When placing an object, we have placing constraints
- A foundation must be partly inside terrain. The test is cumbersome but doable : Make sure the foundation collides with terrain, then ray cast from 4 point located inside the object to the base of the object. If any of the rays hit terrain, the test has failed.
- Other objects must be snapped to at least one object in a group.
Examples:
- A wall must have a foundation or a floor in the cell it's in.
- A floor must have a wall below or a floor next to it.
Those constraints should be hard-coded in a constraint checking function until I find more patterns and begin generalising them
- All objects except foundations must not be inside terrain, even partially. This is a simple Overlap test between the object's collider and the Terrain RigidBody
- Objects from different groups must not be inside each other
- Objects in the same group do not need to test whether they are inside each other or not: the group's constraints take care of all that.
Offset
All objects have an offset driven by their template: that's the Vector3 representing where the GameObject centre is compared to the centre of its 1x1x1m cell. Example: A foundation is always at the bottom of a cell, and 0.5m high. Therefore its cell centre is 0.25 above (Y axis) its GameObject centre.
Offsest can become complex with objects that can rotate, as their rotation can change their offset. Example: A wall cell's centre always has the same Y offset, but its X and Z offset each have 2 possible values, for a total of 4 possible walls, one on each vertical side of the cell.
Snapping
The possible positions of an object are actually easy to calculate:
- When they don't belong to a group (like a foundation), they just need to meet their constraints.
- When we are trying to snap them to a group, their centre is always the group's root + a Vector3 int. No other position is possible:
- First, the groups (plural) a placeable object can snap to should be determined by a fairly generous collider that detects the proximity to other objects, and deduct their BeyondGroup if they have a BeyondComponent.
- Use the current placeable object's position Vector3 pos
- Find the difference with the group's root Vector3 diff
- Round it to a Vector3 integer Vector3Int diffInt
- Use the group's root Vector3 position to determine the centre Vector3 groupRoot + (float)diffInt
- Apply snapping constraints to determine whether the object is place-able: what objects are or are not in neighbouring cells, prevent objects from being put inside others, make sure objects have the support they need (walls in a foundation, and snapping to other walls, etc)
Multi-cells objects
Objects can be bigger than 1x1x1m, in which case their constraints can get more complex. Example : a roof could be a 5m long, 10m wide, 2m high object, occupying a total of 100 cells in the group.
These multi-cell object must contain a list of cells they occupy, so each cell can be checked within the group. These cells can be a simple list of Vector3Int, which is far more efficient than pure Vector3
Object states
All objects have a state:
- Ghost (or placeable): currently being placed, not part of any group until snapped. Turns red when it violates a constraint
- Blueprint: placed but not yet built
- Solid: built for good
It should be possible to have more than one ghost at the same time, to be able to place surfaces for Foundations & Walls, or maybe others. This would allow drag and drop of objects, taking into account whether they can be dragged in 1D, 2D, 3D or not at all. Dragging in 3D might not be needed (can't think of a case)