Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Unity/SpaceCraft/Assets/Editor/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ public static void WebGL_Prod()
}
// --- End Force ---

// Default prod output (existing behavior)
string buildPath = Path.Combine("Builds", "SpaceCraft");
// Default prod output (match Development build location)
string buildPath = Path.Combine("..", "..", "WebSites", "SpaceCraft");
// Allow CI override via env var or CLI
buildPath = ResolveOutputPath(buildPath);
Debug.Log($"[Build] Output path: {buildPath}");
Expand Down
8 changes: 7 additions & 1 deletion Unity/SpaceCraft/Assets/Resources/Prefabs/MagnetView.prefab
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,17 @@ MonoBehaviour:
magnetSoftness: 0
magnetHoleRadius: 0
magnetHoleStrength: 0
_scoreMin: 0
_scoreMin: 0.8
_scoreMax: 1
viewScale: 1
viewScaleInitial: 0.01
magnetMaterial: {fileID: 2100000, guid: 2610998a9e1a94c63a3d4e941db2602e, type: 2}
orbitForce: 0
orbitWidth: 4
orbitEjectStrength: 10
orbitEjectRadius: 10
orbitDeflectStrength: 2
orbitDeflectRadius: 7
--- !u!1 &7411782117926916970
GameObject:
m_ObjectHideFlags: 0
Expand Down
63 changes: 39 additions & 24 deletions Unity/SpaceCraft/Assets/Scripts/Core/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ public class InputManager : MonoBehaviour
[ExposedParameter(
"View Mode",
Category = "View",
Description = "Current view mode: magnets, selection, manual, or attract",
Default = "magnets",
Description = "Current view mode (tab id): about, arrange, view, select, inspect",
Default = "arrange",
Visible = true
)]
public string viewMode = "magnets";
public string viewMode = "arrange";

[ExposedParameter(
"View Seek Position Speed",
Expand Down Expand Up @@ -391,24 +391,28 @@ void FixedUpdate()
ApplyContinuousThrust();


switch (viewMode) {
// Use tab ids only: about, arrange, view, select, inspect
string _mode = (viewMode ?? "").ToLowerInvariant();

case "attract":
switch (_mode) {

case "about":
viewSeekEnabled = true;
ViewSeekAttract();
break;

case "magnets":
case "arrange":
viewSeekEnabled = true;
ViewSeekMagnets();
break;

case "selection":
case "select":
case "inspect":
viewSeekEnabled = true;
ViewSeekSelection();
break;

case "manual":
case "view":
default:
viewSeekEnabled = false;
break;
Expand Down Expand Up @@ -864,29 +868,40 @@ private void ApplyMagnetForces()
ItemView[] allItems = FindObjectsByType<ItemView>(FindObjectsSortMode.None);
if (allItems.Length == 0) return;

// Precompute, for each item, whether any magnet (other than the one applying force)
// would attract/affect it. This lets MagnetView modulate ejection using orbitDeflectStrength.
Dictionary<ItemView, bool> itemAffected = new Dictionary<ItemView, bool>(allItems.Length);
foreach (var itemView in allItems)
{
bool affected = false;
Vector3 pos = itemView.transform.position;
foreach (var m in allMagnets)
{
if (m == null || !m.magnetEnabled) continue;
if (m.WouldAttract(itemView, pos)) { affected = true; break; }
}
itemAffected[itemView] = affected;
}

foreach (MagnetView magnet in allMagnets)
{
if (magnet == null || !magnet.magnetEnabled) continue;

foreach (ItemView itemView in allItems)
{
if (itemView?.GetComponent<Rigidbody>() == null) continue;

Rigidbody rb = itemView.GetComponent<Rigidbody>();
if (rb.isKinematic) continue;

Vector3 magneticForce = magnet.CalculateMagneticForce(itemView, itemView.transform.position);

if (magneticForce.magnitude > 0.001f)
{
rb.AddForce(magneticForce, ForceMode.Force);

if (rb.IsSleeping() && magneticForce.magnitude > 0.1f)
{
rb.WakeUp();
}
}

if (rb.isKinematic) continue;

bool affectedElsewhere = false;
if (itemAffected.TryGetValue(itemView, out bool v)) affectedElsewhere = v;
// If this magnet alone is affecting, we still pass the global flag;
// MagnetView will choose orbitDeflectStrength only for repulsion.
Vector3 magneticForce = magnet.CalculateMagneticForce(itemView, itemView.transform.position, affectedElsewhere);
if (magneticForce.magnitude > 0.001f)
{
rb.AddForce(magneticForce, ForceMode.Force);
if (rb.IsSleeping() && magneticForce.magnitude > 0.1f) rb.WakeUp();
}
if (rb.linearVelocity.magnitude > maxItemVelocity)
{
rb.linearVelocity = rb.linearVelocity.normalized * maxItemVelocity;
Expand Down
15 changes: 15 additions & 0 deletions Unity/SpaceCraft/Assets/Scripts/Core/SpaceCraft.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1208,4 +1208,19 @@ public void PushCameraVelocity(string controllerId, string controllerName, float
{
inputManager.PushCameraVelocity(controllerId, controllerName, panXDelta, panYDelta);
}

/// <summary>
/// Public API to push an item by id with a small delta in world units.
/// Exposed to JS via bridge.updateObject(this.spaceCraft, { "method:PushItem": [itemId, dx, dy] })
/// </summary>
public void PushItem(string itemId, float dx, float dy)
{
if (string.IsNullOrEmpty(itemId)) return;
var itemView = FindItemSafe(itemId);
if (itemView == null) return;
var rb = itemView.GetComponent<Rigidbody>();
if (rb == null) return;
Vector3 newPosition = itemView.transform.position + new Vector3(dx, 0f, dy);
rb.MovePosition(newPosition);
}
}
64 changes: 58 additions & 6 deletions Unity/SpaceCraft/Assets/Scripts/Views/MagnetView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,17 +218,41 @@ public string searchType
)]
[SerializeField] public float orbitEjectStrength = 10f;

[ExposedParameter(
"Orbit Deflect Strength",
Category = "Magnetic Field",
Description = "Reduced outward force applied to items already influenced by one or more magnets.",
Default = 2f,
Visible = true,
Min = 0f,
Max = 100f,
Step = 0.1f
)]
[SerializeField] public float orbitDeflectStrength = 2f;

[ExposedParameter(
"Orbit Eject Radius",
Category = "Magnetic Field",
Description = "Outward force applied to items with scores below the minimum threshold.",
Default = 15f,
Default = 10f,
Visible = true,
Min = 0f,
Max = 100f,
Step = 0.1f
)]
[SerializeField] public float orbitEjectRadius= 10f;

[ExposedParameter(
"Orbit Deflect Radius",
Category = "Magnetic Field",
Description = "Radius for reduced repulsion when item is influenced by other magnets.",
Default = 7f,
Visible = true,
Min = 0f,
Max = 100f,
Step = 0.1f
)]
[SerializeField] public float orbitEjectRadius= 7f;
[SerializeField] public float orbitDeflectRadius = 7f;

[SerializeField] private float _scoreMin = 0.8f;
[ExposedParameter(
Expand Down Expand Up @@ -435,6 +459,16 @@ public bool IsItemEligible(Item item)
/// Apply magnetic force to an item based on distance and relevance
/// </summary>
public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition)
{
// Backwards-compat overload: default no deflection knowledge
return CalculateMagneticForce(itemView, itemPosition, false);
}

/// <summary>
/// Calculates magnetic force. If itemAffectedByOtherMagnets is true, repulsion inside eject radius
/// will use orbitDeflectStrength instead of orbitEjectStrength.
/// </summary>
public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition, bool itemAffectedByOtherMagnets)
{
if (!magnetEnabled || itemView?.Model == null) return Vector3.zero;

Expand All @@ -454,15 +488,17 @@ public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition)
// EJECTION LOGIC for items that don't meet the minimum score
if (score < scoreMin)
{
// Only apply ejection inside the orbitEjectRadius
if (orbitEjectStrength <= 0.001f || distance >= orbitEjectRadius) return Vector3.zero;
// Only apply ejection/deflection inside the appropriate radius
float radius = itemAffectedByOtherMagnets ? orbitDeflectRadius : orbitEjectRadius;
float strengthParam = itemAffectedByOtherMagnets ? orbitDeflectStrength : orbitEjectStrength;
if (strengthParam <= 0.001f || distance >= radius) return Vector3.zero;

// Ejection force is strongest for items with score 0, and falls to 0 as score approaches scoreMin
float scoreFactor = 1f - (score / Mathf.Max(scoreMin, 0.0001f));
scoreFactor = Mathf.Clamp01(scoreFactor);

// Have the ejection force fall off towards the edge of the ejection radius
float ejectU = distance / Mathf.Max(orbitEjectRadius, 0.0001f); // 0 at center, 1 at eject edge
float ejectU = distance / Mathf.Max(radius, 0.0001f); // 0 at center, 1 at edge
float s = Mathf.Clamp01(magnetSoftness);
float ejectEdgeWidth = 0.5f * s;
float ejectEdgeFactor;
Expand All @@ -477,7 +513,7 @@ public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition)
ejectEdgeFactor = (ejectU < 1f) ? 1f : 0f;
}

float forceStrength = orbitEjectStrength * scoreFactor * ejectEdgeFactor;
float forceStrength = strengthParam * scoreFactor * ejectEdgeFactor;

// Return outward force (away from magnet)
return -toMagnet.normalized * forceStrength;
Expand Down Expand Up @@ -584,6 +620,22 @@ public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition)
return Vector3.zero;
}

/// <summary>
/// Returns true if this magnet would attract or orbit the item (meets score threshold
/// and lies within the magnet's influence radius).
/// </summary>
public bool WouldAttract(ItemView itemView, Vector3 itemPosition)
{
if (!magnetEnabled || itemView?.Model == null) return false;
float score = CalculateItemScore(itemView.Model);
if (score < scoreMin || score > scoreMax) return false;
Vector3 toMagnet = transform.position - itemPosition;
float distance = toMagnet.magnitude;
float innerR = Mathf.Max(0f, magnetHoleRadius);
float outerR = Mathf.Max(innerR, magnetRadius);
return distance < outerR;
}

// Cubic Hermite smoothstep
private static float SmoothStep(float edge0, float edge1, float x)
{
Expand Down
41 changes: 41 additions & 0 deletions Unity/SpaceCraft/Assets/StreamingAssets/SpaceCraft/spacecraft.js
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,47 @@ class SpaceCraftSim {
}
})

// Push a single item by ID in simulator world space (from controller Select tab joystick)
.on('broadcast', { event: 'pushItem' }, (data) => {
try {
const payload = data && data.payload || {};
try { console.log('[Sim] pushItem event received', JSON.parse(JSON.stringify(payload))); } catch {}

// Respect targeting if provided
if (payload.targetSimulatorId && payload.targetSimulatorId !== this.identity.clientId) {
try { console.log('[Sim] pushItem ignored: targeted for other simulator', { targetSimulatorId: payload.targetSimulatorId, myId: this.identity.clientId }); } catch {}
return;
}

const itemId = payload.itemId;
const dx = Number(payload.deltaX);
const dy = Number(payload.deltaY);

if (!itemId || !isFinite(dx) || !isFinite(dy)) {
try { console.warn('[Sim] pushItem invalid payload', { itemId, dx, dy }); } catch {}
return;
}

if (!this.spaceCraft) {
try { console.warn('[Sim] pushItem aborted: this.spaceCraft is not ready'); } catch {}
return;
}

// Prefer calling the exposed C# method via bridge updateObject (explicit)
try { console.log('[Sim] pushItem invoking bridge.updateObject → method:PushItem', { itemId, dx, dy }); } catch {}
try {
bridge.updateObject(this.spaceCraft, {
"method:PushItem": [itemId, dx, dy]
});
try { console.log('[Sim] pushItem dispatched to Unity'); } catch {}
} catch (e) {
try { console.error('[Sim] pushItem bridge call failed', e); } catch {}
}
} catch (outer) {
try { console.error('[Sim] pushItem handler error', outer); } catch {}
}
})

.on('broadcast', { event: 'setViewMode' }, (data) => {
if (data.payload.targetSimulatorId && data.payload.targetSimulatorId !== this.identity.clientId) {
return;
Expand Down
4 changes: 2 additions & 2 deletions WebSites/SpaceCraft/Build/SpaceCraft.data
Git LFS file not shown
Loading