mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-25 06:40:28 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
325 lines
8.8 KiB
C#
325 lines
8.8 KiB
C#
using Facepunch.ActionGraphs;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace Sandbox;
|
|
|
|
public static class SceneUtility
|
|
{
|
|
/// <summary>
|
|
/// Find all "__guid" guids, and replace them with new guids. This is used to make GameObject serializations unique,
|
|
/// so when you duplicate stuff, it copies over uniquely and keeps associations.
|
|
/// </summary>
|
|
public static Dictionary<Guid, Guid> MakeIdGuidsUnique( JsonObject json, Guid? rootGuid = null )
|
|
{
|
|
var translate = CreateUniqueGuidLookup( json, rootGuid );
|
|
|
|
//
|
|
// Find every guid and translate them, but only if they're in our
|
|
// guid dictionary.
|
|
//
|
|
Sandbox.Json.WalkJsonTree( json, ( k, v ) =>
|
|
{
|
|
if ( !v.TryGetValue<Guid>( out var guid ) ) return v;
|
|
if ( !translate.TryGetValue( guid, out var updatedGuid ) ) return v;
|
|
|
|
return updatedGuid;
|
|
} );
|
|
|
|
return translate;
|
|
}
|
|
|
|
internal static Dictionary<Guid, Guid> CreateUniqueGuidLookup( JsonObject json, Guid? rootGuid = null )
|
|
{
|
|
Dictionary<Guid, Guid> translate = new();
|
|
|
|
//
|
|
// This is a prefab that is being spawned into the scene. As such, it already has a guid.
|
|
// So we need to replace whatever the prefab's guid is with the real id, and make sure
|
|
// it doesn't generate a new one for it.. and make sure that it updates all of the children
|
|
// so they correctly reference the new guid.
|
|
//
|
|
if ( rootGuid.HasValue )
|
|
{
|
|
if ( json.TryGetPropertyValue( "__guid", out var node ) && node is JsonValue jsv && jsv.TryGetValue<Guid>( out var guid ) )
|
|
{
|
|
translate[guid] = rootGuid.Value;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We want to skip ActionGraph Guids if they're cached, so we can re-use the cached versions.
|
|
//
|
|
if ( ActionGraph.SerializationOptions.Cache is { } cache )
|
|
{
|
|
foreach ( var guid in cache.Guids )
|
|
{
|
|
translate[guid] = guid;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Find all guids with "__guid" as their name. Add them to translate
|
|
// with a new target value.
|
|
//
|
|
Sandbox.Json.WalkJsonTree( json, ( k, v ) =>
|
|
{
|
|
if ( k == GameObject.JsonKeys.Id )
|
|
{
|
|
if ( v.TryGetValue<Guid>( out var guid ) && !translate.ContainsKey( guid ) )
|
|
{
|
|
translate[guid] = Guid.NewGuid();
|
|
}
|
|
}
|
|
|
|
return v;
|
|
},
|
|
( k, v ) =>
|
|
{
|
|
// GameObjects & Components that are part of prefab instances have their id stored in the PrefabToInstanceId lookup in JSON.
|
|
// We need to remap those as well in addtion to __guid id's.
|
|
if ( k == GameObject.JsonKeys.PrefabIdToInstanceId )
|
|
{
|
|
foreach ( var (prefabId, instanceID) in v )
|
|
{
|
|
if ( instanceID.AsValue().TryGetValue<Guid>( out var guid ) && !translate.ContainsKey( guid ) )
|
|
{
|
|
translate[guid] = Guid.NewGuid();
|
|
}
|
|
}
|
|
}
|
|
|
|
return v;
|
|
} );
|
|
|
|
return translate;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find all "Id" guids, and replace them with new guids. This is used to make GameObject serializations unique,
|
|
/// so when you duplicate stuff, it copies over uniquely and keeps associations.
|
|
/// </summary>
|
|
[Obsolete( "Use MakeIdGuidsUnique" )]
|
|
public static void MakeGameObjectsUnique( JsonObject json, Guid? rootGuid = null )
|
|
{
|
|
MakeIdGuidsUnique( json, rootGuid );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the passed in GameObject
|
|
/// </summary>
|
|
[System.Obsolete( "Please use GameObject.Clone( ... )" )]
|
|
public static GameObject Instantiate( GameObject template, Transform transform )
|
|
{
|
|
Assert.NotNull( Game.ActiveScene, "No Active Scene" );
|
|
|
|
using var batchGroup = CallbackBatch.Batch();
|
|
|
|
JsonObject json = null;
|
|
|
|
if ( template is PrefabScene prefabScene && prefabScene.Source is PrefabFile prefabFile )
|
|
{
|
|
json = prefabFile.RootObject;
|
|
}
|
|
else
|
|
{
|
|
json = template.Serialize();
|
|
}
|
|
|
|
if ( json is not null )
|
|
{
|
|
MakeIdGuidsUnique( json );
|
|
}
|
|
|
|
var go = new GameObject();
|
|
|
|
if ( json is not null )
|
|
{
|
|
go.Deserialize( json );
|
|
}
|
|
|
|
go.LocalTransform = transform.WithScale( transform.Scale * go.LocalScale );
|
|
|
|
if ( template is PrefabScene prefabScene1 )
|
|
{
|
|
go.InitPrefabInstance( prefabScene1.Source.ResourcePath, false );
|
|
}
|
|
|
|
go.Parent = Game.ActiveScene;
|
|
|
|
return go;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the passed in GameObject
|
|
/// </summary>
|
|
[System.Obsolete( "Please use GameObject.Clone( ... )" )]
|
|
public static GameObject Instantiate( GameObject template ) => Instantiate( template, Transform.Zero );
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the passed in GameObject
|
|
/// </summary>
|
|
[System.Obsolete( "Please use GameObject.Clone( ... )" )]
|
|
public static GameObject Instantiate( GameObject template, Vector3 position, Rotation rotation )
|
|
=> Instantiate( template, new Transform( position, rotation, 1.0f ) );
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the passed in GameObject
|
|
/// </summary>
|
|
[System.Obsolete( "Please use GameObject.Clone( ... )" )]
|
|
public static GameObject Instantiate( GameObject template, Vector3 position )
|
|
=> Instantiate( template, new Transform( position, Rotation.Identity, 1.0f ) );
|
|
|
|
/// <summary>
|
|
/// Get a (cached) scene from a PrefabFile
|
|
/// </summary>
|
|
public static PrefabScene GetPrefabScene( PrefabFile prefabFile ) => prefabFile.GetScene();
|
|
|
|
/// <summary>
|
|
/// Render a GameObject to a bitmap. This is usually used for easily rendering "previews" of GameObjects,
|
|
/// for things like saving thumbnails etc.
|
|
/// </summary>
|
|
static void RenderToBitmap( Bitmap bitmap, Func<GameObject> func )
|
|
{
|
|
var scene = Scene.CreateEditorScene();
|
|
scene.Name = "RenderGameObjectToBitmap";
|
|
try
|
|
{
|
|
using var sceneScope = scene.Push();
|
|
|
|
CameraComponent camera = default;
|
|
|
|
// camera
|
|
{
|
|
var go = new GameObject( true, "camera" );
|
|
camera = go.AddComponent<CameraComponent>();
|
|
camera.BackgroundColor = Color.Transparent;
|
|
camera.WorldRotation = new Angles( 20, 180 + 45, 0 );
|
|
camera.FieldOfView = 50.0f;
|
|
camera.ZFar = 15000.0f;
|
|
camera.ZNear = 0.1f;
|
|
|
|
var sharpen = go.AddComponent<Sharpen>( true );
|
|
sharpen.Scale = 0.2f;
|
|
|
|
go.AddComponent<AmbientOcclusion>( true );
|
|
go.AddComponent<ScreenSpaceReflections>( true ); // I don't think this'll work?
|
|
go.AddComponent<Bloom>( true );
|
|
|
|
}
|
|
|
|
// ambient light
|
|
{
|
|
var go = new GameObject( true, "ambient" );
|
|
var c = go.AddComponent<AmbientLight>();
|
|
c.Color = Color.White * 0.2f;
|
|
}
|
|
|
|
// sun light
|
|
{
|
|
var go = new GameObject( true, "sun" );
|
|
var sun = go.AddComponent<DirectionalLight>();
|
|
sun.Shadows = true;
|
|
sun.WorldRotation = new Angles( 60, 45, 0 );
|
|
sun.LightColor = Color.White * 0.4f;
|
|
}
|
|
|
|
// envmap
|
|
{
|
|
var go = new GameObject( true, "envmap" );
|
|
var c = go.AddComponent<EnvmapProbe>();
|
|
c.Texture = Texture.Load( "textures/cubemaps/default2.vtex" );
|
|
c.Bounds = BBox.FromPositionAndSize( Vector3.Zero, 100000 );
|
|
}
|
|
|
|
GameObject o = default;
|
|
|
|
// prefab spawn
|
|
{
|
|
o = func();
|
|
o.WorldPosition = 0;
|
|
}
|
|
|
|
// tick tick
|
|
float t = 0;
|
|
for ( int i = 0; i < 8; i++ )
|
|
{
|
|
scene.EditorTick( t += 0.1f, 0.1f );
|
|
}
|
|
|
|
// place the camera
|
|
{
|
|
var bounds = o.GetBounds();
|
|
|
|
var distance = MathX.SphereCameraDistance( bounds.Size.Length * 0.5f, camera.FieldOfView );
|
|
var aspect = bitmap.Width / bitmap.Height;
|
|
if ( aspect > 1 ) distance *= aspect;
|
|
|
|
camera.WorldPosition = (o.WorldRotation * bounds.Center) + camera.WorldRotation.Forward * -distance;
|
|
}
|
|
|
|
// render twice, for any temporal shit to kick in
|
|
camera.RenderToBitmap( bitmap );
|
|
scene.EditorTick( t += 0.1f, 0.1f );
|
|
camera.RenderToBitmap( bitmap );
|
|
|
|
scene.Destroy();
|
|
}
|
|
finally
|
|
{
|
|
scene.Destroy();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render a GameObject to a bitmap. This is usually used for easily rendering "previews" of GameObjects,
|
|
/// for things like saving thumbnails etc.
|
|
/// </summary>
|
|
public static void RenderGameObjectToBitmap( GameObject objSource, Bitmap bitmap )
|
|
{
|
|
if ( objSource == null ) return;
|
|
if ( bitmap == null ) return;
|
|
|
|
Sandbox.Rendering.TextureStreaming.ExecuteWithDisabled( () =>
|
|
{
|
|
RenderToBitmap( bitmap, () =>
|
|
{
|
|
var o = objSource.Clone();
|
|
o.WorldPosition = 0;
|
|
return o;
|
|
} );
|
|
} );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render a Model to a bitmap. This is usually used for easily rendering "previews" of Models for thumbnails
|
|
/// </summary>
|
|
public static void RenderModelBitmap( Model model, Bitmap bitmap )
|
|
{
|
|
if ( model == null ) return;
|
|
if ( bitmap == null ) return;
|
|
|
|
Sandbox.Rendering.TextureStreaming.ExecuteWithDisabled( () =>
|
|
{
|
|
RenderToBitmap( bitmap, () =>
|
|
{
|
|
var o = new GameObject();
|
|
o.AddComponent<ModelRenderer>().Model = model;
|
|
return o;
|
|
} );
|
|
} );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run an action inside a batch group. A batchgroup is used with GameObject and Components to
|
|
/// make sure that their OnEnable/OnDisable and other callbacks are called in a deterministic order,
|
|
/// and that they can find each other during creation.
|
|
/// </summary>
|
|
public static void RunInBatchGroup( Action action )
|
|
{
|
|
using ( CallbackBatch.Isolated() )
|
|
{
|
|
action?.Invoke();
|
|
}
|
|
}
|
|
}
|