Lower entity-forward follow cameras generically
This commit is contained in:
parent
b19ac8cc6c
commit
35ff288aab
4 changed files with 133 additions and 11 deletions
|
|
@ -51,6 +51,14 @@ temporal pass must also advertise its current-sample, history,
|
|||
previous-view-projection, and reset-history ports; partial temporal programs fail
|
||||
closed.
|
||||
|
||||
The provider selects camera semantics and framing from its authoritative mode.
|
||||
`planar.top-down-follow.v1` derives a downward presentation from the advertised
|
||||
target and framing values. `perspective.entity-forward-follow.v1` derives its
|
||||
view direction from the presented entity rotation and applies the same generic
|
||||
distance, screen-position, damping, and lens contract. EveUnity owns only the
|
||||
native camera transform. It cannot collapse distinct provider modes into one
|
||||
camera opinion or infer a product camera from entity kind.
|
||||
|
||||
## Shared Paths
|
||||
|
||||
Initial connect discovers a Verse endpoint and selects a provider surface by
|
||||
|
|
|
|||
|
|
@ -80,7 +80,9 @@ namespace GameCult.Eve.UnityScene
|
|||
return false;
|
||||
}
|
||||
|
||||
if (activeWorld.CameraRig == "planar.top-down-follow.v1" && !HasValidPlanarContract(activeWorld))
|
||||
if ((activeWorld.CameraRig == "planar.top-down-follow.v1" ||
|
||||
activeWorld.CameraRig == "perspective.entity-forward-follow.v1") &&
|
||||
!HasValidFramedFollowContract(activeWorld))
|
||||
{
|
||||
ReleaseCamera();
|
||||
RestoreAmbientEnvironment();
|
||||
|
|
@ -88,6 +90,7 @@ namespace GameCult.Eve.UnityScene
|
|||
}
|
||||
if (!string.IsNullOrWhiteSpace(activeWorld.CameraRig) &&
|
||||
activeWorld.CameraRig != "planar.top-down-follow.v1" &&
|
||||
activeWorld.CameraRig != "perspective.entity-forward-follow.v1" &&
|
||||
activeWorld.CameraRig != "arpg.orbital-follow.v1" &&
|
||||
activeWorld.CameraRig != "third-person-orbit")
|
||||
{
|
||||
|
|
@ -139,6 +142,8 @@ namespace GameCult.Eve.UnityScene
|
|||
}
|
||||
if (activeWorld.CameraRig == "planar.top-down-follow.v1")
|
||||
return ApplyPlanarTopDown(activeWorld, camera, cameraComponent, player.position, deltaTime);
|
||||
if (activeWorld.CameraRig == "perspective.entity-forward-follow.v1")
|
||||
return ApplyEntityForwardFollow(activeWorld, camera, cameraComponent, player, deltaTime);
|
||||
|
||||
var bounds = CalculateVisualBounds(
|
||||
player,
|
||||
|
|
@ -197,7 +202,50 @@ namespace GameCult.Eve.UnityScene
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool HasValidPlanarContract(EveUnityPlayableWorldProjection world) =>
|
||||
private static bool ApplyEntityForwardFollow(
|
||||
EveUnityPlayableWorldProjection world,
|
||||
Transform camera,
|
||||
Camera? cameraComponent,
|
||||
Transform target,
|
||||
float deltaTime)
|
||||
{
|
||||
var resolvedDistance = world.CameraDistance;
|
||||
var verticalFov = world.CameraVerticalFieldOfViewDegrees;
|
||||
if (cameraComponent != null)
|
||||
{
|
||||
cameraComponent.orthographic = false;
|
||||
cameraComponent.lensShift = Vector2.zero;
|
||||
cameraComponent.fieldOfView = verticalFov;
|
||||
cameraComponent.nearClipPlane = world.CameraNearClipPlane;
|
||||
cameraComponent.farClipPlane = world.CameraFarClipPlane;
|
||||
}
|
||||
|
||||
var forward = target.forward;
|
||||
forward.y = 0f;
|
||||
if (forward.sqrMagnitude <= 0.0001f)
|
||||
forward = Vector3.forward;
|
||||
forward.Normalize();
|
||||
var rotation = Quaternion.LookRotation(forward, Vector3.up);
|
||||
var right = rotation * Vector3.right;
|
||||
var aspect = cameraComponent != null ? cameraComponent.aspect : 16f / 9f;
|
||||
var verticalHalfExtent = resolvedDistance * Mathf.Tan(verticalFov * 0.5f * Mathf.Deg2Rad);
|
||||
var horizontalHalfExtent = verticalHalfExtent * Mathf.Max(0.01f, aspect);
|
||||
var horizontalOffset = (Mathf.Clamp01(world.CameraTargetScreenX) - 0.5f) * 2f * horizontalHalfExtent;
|
||||
var verticalOffset = (Mathf.Clamp01(world.CameraTargetScreenY) - 0.5f) * 2f * verticalHalfExtent;
|
||||
var desiredPosition = target.position -
|
||||
(forward * resolvedDistance) -
|
||||
(right * horizontalOffset) -
|
||||
(Vector3.up * verticalOffset);
|
||||
var damping = Mathf.Max(0f, world.CameraPositionDamping);
|
||||
var t = deltaTime <= 0f || damping <= 0f
|
||||
? 1f
|
||||
: 1f - Mathf.Exp(-damping * deltaTime);
|
||||
camera.position = Vector3.Lerp(camera.position, desiredPosition, t);
|
||||
camera.rotation = rotation;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool HasValidFramedFollowContract(EveUnityPlayableWorldProjection world) =>
|
||||
world.CameraDistance > 0f &&
|
||||
world.CameraVerticalFieldOfViewDegrees > 0f &&
|
||||
world.CameraVerticalFieldOfViewDegrees < 180f &&
|
||||
|
|
|
|||
|
|
@ -1265,6 +1265,65 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlayableWorldCameraRigFramesEntityForwardPerspectiveWithoutProviderTypes()
|
||||
{
|
||||
var rootObject = new GameObject("generic-forward-world-root");
|
||||
var hostObject = new GameObject("generic-forward-client");
|
||||
var cameraObject = new GameObject("generic-forward-camera");
|
||||
hostObject.SetActive(false);
|
||||
try
|
||||
{
|
||||
var provider = hostObject.AddComponent<FakePlayableWorldProviderComponent>();
|
||||
var nativeAssets = hostObject.AddComponent<FixedNativeAssetProviderComponent>();
|
||||
provider.Set(
|
||||
new EveUnitySceneProviderSurfaceDocument(
|
||||
PlayableArpgDocument(
|
||||
cameraRig: "perspective.entity-forward-follow.v1",
|
||||
cameraDistance: "30",
|
||||
cameraTargetScreenX: "0.64",
|
||||
cameraTargetScreenY: "0.81",
|
||||
cameraPositionDamping: "0",
|
||||
cameraNearClipPlane: "1",
|
||||
cameraFarClipPlane: "4096"),
|
||||
Advertisement("aetheria.daemon.game"),
|
||||
"cultmesh://generic/eve/surfaces/forward",
|
||||
1),
|
||||
new EveUnityPlayableWorldAssetManifestDocument(
|
||||
"cultmesh://generic/assets/manifest",
|
||||
Array.Empty<EveUnityPlayableWorldAssetManifestDocumentEntry>(),
|
||||
"generic"));
|
||||
|
||||
var host = hostObject.AddComponent<EveUnityPlayableWorldClientHost>();
|
||||
host.Configure(rootObject.transform, provider, provider, provider, provider, nativeAssets);
|
||||
host.Connect();
|
||||
hostObject.SetActive(true);
|
||||
var camera = cameraObject.AddComponent<Camera>();
|
||||
camera.aspect = 16f / 9f;
|
||||
var rig = hostObject.AddComponent<EveUnityPlayableWorldCameraRig>();
|
||||
rig.Host = host;
|
||||
rig.CameraTransform = cameraObject.transform;
|
||||
|
||||
var player = rootObject.GetComponentInChildren<EveUnityPlayableWorldEntityMarker>();
|
||||
Assert.That(player, Is.Not.Null);
|
||||
Assert.That(rig.ApplyRig(0f), Is.True);
|
||||
Assert.That(Vector3.Dot(cameraObject.transform.forward, player!.transform.forward), Is.GreaterThan(0.999f));
|
||||
Assert.That(camera.fieldOfView, Is.EqualTo(60f).Within(0.001f));
|
||||
Assert.That(camera.nearClipPlane, Is.EqualTo(1f).Within(0.001f));
|
||||
Assert.That(camera.farClipPlane, Is.EqualTo(4096f).Within(0.001f));
|
||||
var viewport = camera.WorldToViewportPoint(player.transform.position);
|
||||
Assert.That(viewport.x, Is.EqualTo(0.64f).Within(0.001f));
|
||||
Assert.That(viewport.y, Is.EqualTo(0.81f).Within(0.001f));
|
||||
Assert.That(cameraObject.transform.position.y, Is.LessThan(player.transform.position.y));
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(cameraObject);
|
||||
UnityEngine.Object.DestroyImmediate(hostObject);
|
||||
UnityEngine.Object.DestroyImmediate(rootObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProviderPrefabKeepsAuthoredScaleWhileFallbackUsesSemanticRadius()
|
||||
{
|
||||
|
|
@ -1773,7 +1832,14 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
bool includeRaider = true,
|
||||
string playerPosition = "0,0,0",
|
||||
string skyboxAssetRef = "",
|
||||
string reflectionAssetRef = "")
|
||||
string reflectionAssetRef = "",
|
||||
string cameraRig = "planar.top-down-follow.v1",
|
||||
string cameraDistance = "150",
|
||||
string cameraTargetScreenX = "0.9",
|
||||
string cameraTargetScreenY = "0.55",
|
||||
string cameraPositionDamping = "5",
|
||||
string cameraNearClipPlane = "0.3",
|
||||
string cameraFarClipPlane = "4096")
|
||||
{
|
||||
var playableChildren = new List<EveSurfaceComponent>
|
||||
{
|
||||
|
|
@ -1910,15 +1976,15 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
["zoneRenderSchema"] = "gamecult.aetheria.zone_render.v1",
|
||||
["assetManifest"] = "cultmesh://aetheria/assets/manifest",
|
||||
["inputProfile"] = "arpg-third-person",
|
||||
["cameraRig"] = "planar.top-down-follow.v1",
|
||||
["cameraRig"] = cameraRig,
|
||||
["cameraTargetEntityId"] = "player-vanguard",
|
||||
["cameraDistance"] = "150",
|
||||
["cameraDistance"] = cameraDistance,
|
||||
["cameraVerticalFieldOfViewDegrees"] = "60",
|
||||
["cameraTargetScreenX"] = "0.9",
|
||||
["cameraTargetScreenY"] = "0.55",
|
||||
["cameraPositionDamping"] = "5",
|
||||
["cameraNearClipPlane"] = "0.3",
|
||||
["cameraFarClipPlane"] = "4096",
|
||||
["cameraTargetScreenX"] = cameraTargetScreenX,
|
||||
["cameraTargetScreenY"] = cameraTargetScreenY,
|
||||
["cameraPositionDamping"] = cameraPositionDamping,
|
||||
["cameraNearClipPlane"] = cameraNearClipPlane,
|
||||
["cameraFarClipPlane"] = cameraFarClipPlane,
|
||||
["ambientLightColor"] = "0.2,0.2,0.2",
|
||||
["ambientLightIntensity"] = "1.46",
|
||||
["skyboxAssetRef"] = skyboxAssetRef,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "org.gamecult.eve.unity-scene",
|
||||
"displayName": "Eve Unity Scene Lowering",
|
||||
"version": "0.3.38",
|
||||
"version": "0.3.39",
|
||||
"unity": "2021.3",
|
||||
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
|
||||
"dependencies": {
|
||||
|
|
|
|||
Reference in a new issue