Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions Assets/StraightFour/Testing/WorldTests/WorldTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,93 @@ public IEnumerator WorldTests_World()
Assert.IsTrue(world.cameraManager == null);
Assert.IsTrue(world.materialManager == null);
}

[UnityTest]
public IEnumerator WorldTests_WorldOffsetUpdate()
{
// Initialize World Engine and Load World.
GameObject WEGO = new GameObject();
StraightFour we = WEGO.AddComponent<StraightFour>();
we.skyMaterial = AssetDatabase.LoadAssetAtPath<Material>("Assets/StraightFour/Environment/Materials/Skybox.mat");
we.liteProceduralSkyMaterial = AssetDatabase.LoadAssetAtPath<Material>("Assets/StraightFour/Environment/Materials/LiteProceduralSkybox.mat");
yield return null;
StraightFour.LoadWorld("test");

// Create a character entity
GameObject characterGO = new GameObject("TestCharacter");
CharacterEntity character = characterGO.AddComponent<CharacterEntity>();
character.Initialize(System.Guid.NewGuid(), null, Vector3.zero, Quaternion.identity, Vector3.zero);
character.SetInteractionState(FiveSQD.StraightFour.Entity.BaseEntity.InteractionState.Physical);

// Set the tracked character
StraightFour.ActiveWorld.SetTrackedCharacterEntity(character);
StraightFour.ActiveWorld.enableAutoWorldOffsetUpdate = true;
StraightFour.ActiveWorld.worldOffsetUpdateThreshold = 100f;

// Verify initial offset is zero
Assert.AreEqual(Vector3.zero, StraightFour.ActiveWorld.worldOffset);

// Move character beyond threshold (logical position)
character.SetPosition(new Vector3(150, 0, 0), false, false);

// Unity position should be (150, 0, 0) with offset (0, 0, 0)
Assert.AreEqual(new Vector3(150, 0, 0), character.transform.position);

// Wait a frame for Update to run
yield return null;

// Verify world offset was updated to recenter Unity position at origin
// New offset should be (0, 0, 0) - (150, 0, 0) = (-150, 0, 0)
Vector3 expectedOffset = new Vector3(-150, 0, 0);
Assert.AreEqual(expectedOffset, StraightFour.ActiveWorld.worldOffset);

// Character should now be at Unity origin while maintaining logical position
Assert.AreEqual(Vector3.zero, character.transform.position);
Assert.AreEqual(new Vector3(150, 0, 0), character.GetPosition(false));

// Move character to logical (160, 0, 10) - still close to Unity origin
character.SetPosition(new Vector3(160, 0, 10), false, false);

// Unity position should be (160, 0, 10) + (-150, 0, 0) = (10, 0, 10)
Assert.AreEqual(new Vector3(10, 0, 10), character.transform.position);

// Wait a frame for Update to run
yield return null;

// Offset should remain the same since Unity distance from origin is small
Assert.AreEqual(expectedOffset, StraightFour.ActiveWorld.worldOffset);

// Move character far again to logical (300, 0, 0)
character.SetPosition(new Vector3(300, 0, 0), false, false);

// Unity position should be (300, 0, 0) + (-150, 0, 0) = (150, 0, 0)
Assert.AreEqual(new Vector3(150, 0, 0), character.transform.position);

// Wait a frame for Update to run
yield return null;

// Verify offset updated again: new offset = (-150, 0, 0) - (150, 0, 0) = (-300, 0, 0)
expectedOffset = new Vector3(-300, 0, 0);
Assert.AreEqual(expectedOffset, StraightFour.ActiveWorld.worldOffset);

// Character should be back at Unity origin
Assert.AreEqual(Vector3.zero, character.transform.position);
Assert.AreEqual(new Vector3(300, 0, 0), character.GetPosition(false));

// Test disabling auto-update
StraightFour.ActiveWorld.enableAutoWorldOffsetUpdate = false;
character.SetPosition(new Vector3(500, 0, 0), false, false);

// Unity position should be (500, 0, 0) + (-300, 0, 0) = (200, 0, 0)
Assert.AreEqual(new Vector3(200, 0, 0), character.transform.position);

// Wait a frame for Update to run
yield return null;

// Offset should NOT have changed
Assert.AreEqual(new Vector3(-300, 0, 0), StraightFour.ActiveWorld.worldOffset);

// Character should still be at Unity (200, 0, 0)
Assert.AreEqual(new Vector3(200, 0, 0), character.transform.position);
}
}
59 changes: 59 additions & 0 deletions Assets/StraightFour/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,23 @@ public Vector3 worldOffset

private Vector3 _worldOffset;

/// <summary>
/// The character entity being tracked for world offset updates.
/// </summary>
private CharacterEntity trackedCharacterEntity;

/// <summary>
/// The distance threshold from origin that triggers a world offset update.
/// </summary>
[Tooltip("The distance threshold from origin that triggers a world offset update.")]
public float worldOffsetUpdateThreshold = 1000f;

/// <summary>
/// Whether or not to enable automatic world offset updates.
/// </summary>
[Tooltip("Whether or not to enable automatic world offset updates.")]
public bool enableAutoWorldOffsetUpdate = true;

/// <summary>
/// The GameObject for the mesh manager.
/// </summary>
Expand Down Expand Up @@ -371,6 +388,48 @@ public void Initialize(WorldInfo worldInfo)
siteName = worldInfo.siteName;
}

/// <summary>
/// Set the character entity to track for automatic world offset updates.
/// </summary>
/// <param name="entity">The character entity to track.</param>
public void SetTrackedCharacterEntity(CharacterEntity entity)
{
trackedCharacterEntity = entity;
}

/// <summary>
/// Update method to check tracked character distance and update world offset if needed.
/// </summary>
void Update()
{
if (!enableAutoWorldOffsetUpdate || trackedCharacterEntity == null)
{
return;
}

// Get the Unity position of the tracked character entity
Vector3 unityPosition = trackedCharacterEntity.transform.position;

// Calculate distance from Unity origin (0, 0, 0)
float distanceFromOrigin = unityPosition.magnitude;

// If beyond threshold, update world offset to recenter around character
if (distanceFromOrigin > worldOffsetUpdateThreshold)
{
// Calculate new offset to recenter the Unity origin at the character
// We want Unity position to be (0, 0, 0) while preserving logical position
// Unity = Logical + offset
// Target: Unity' = (0, 0, 0), Logical' = Logical (preserved by setter)
// We have: Logical = Unity - offset
// So: Unity' = Logical + offset' = (Unity - offset) + offset' = (0, 0, 0)
// Therefore: offset' = offset - Unity
Vector3 newOffset = new Vector3(worldOffset.x - unityPosition.x, worldOffset.y, worldOffset.z - unityPosition.z);

// Update the world offset
worldOffset = newOffset;
}
}

/// <summary>
/// Unload the world.
/// </summary>
Expand Down
52 changes: 52 additions & 0 deletions docs/configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,58 @@ WorldInfo worldInfo = new WorldInfo
bool success = StraightFour.LoadWorld("MyWorld", worldInfo);
```

### World Offset Auto-Update

The World class supports automatic world offset updates to maintain floating-point precision as characters move far from the Unity origin. This feature helps prevent precision-related issues and ensures smooth gameplay regardless of how far entities travel in the world.

#### Configuration

```csharp
// Enable automatic world offset updates
StraightFour.ActiveWorld.enableAutoWorldOffsetUpdate = true;

// Set the distance threshold (in Unity units) that triggers an offset update
StraightFour.ActiveWorld.worldOffsetUpdateThreshold = 1000f;

// Register a character entity to track
CharacterEntity playerCharacter = // ... your player character
StraightFour.ActiveWorld.SetTrackedCharacterEntity(playerCharacter);
```

#### How It Works

1. **Distance Monitoring**: The World's Update() method continuously monitors the tracked character's Unity position (transform.position)
2. **Threshold Check**: When the character's distance from the Unity origin (0, 0, 0) exceeds the threshold, an update is triggered
3. **Offset Update**: The world offset is adjusted to recenter the Unity coordinate system at the character's location
4. **Position Preservation**: All entities' logical (world) positions are preserved while their Unity positions are shifted to stay near the origin

**Example:**
- Character moves to Unity position (1500, 0, 0) with current offset (0, 0, 0)
- Distance 1500 exceeds threshold of 1000, triggering an update
- New offset is set to (-1500, 0, 0)
- Character's Unity position becomes (0, 0, 0) while maintaining logical position (1500, 0, 0)

#### Benefits

- **Improved Precision**: Keeps Unity transform positions close to origin, avoiding floating-point precision loss
- **Seamless Experience**: Characters can travel unlimited distances without precision degradation
- **Automatic**: No manual intervention needed once configured
- **Configurable**: Threshold can be adjusted based on world scale and precision requirements

#### Default Values

```csharp
public float worldOffsetUpdateThreshold = 1000f; // Unity units
public bool enableAutoWorldOffsetUpdate = true; // Enabled by default
```

#### Notes

- Only horizontal (X, Z) axes are recentered; Y-axis offset remains unchanged
- The feature can be disabled by setting `enableAutoWorldOffsetUpdate = false`
- The tracked character entity can be changed at runtime using `SetTrackedCharacterEntity()`
- If no character is tracked, automatic updates will not occur

## Unity Project Configuration

### Package Dependencies
Expand Down