#[cfg(feature = "protocol-agent")]
mod agent;
#[cfg(feature = "protocol-agent")]
pub use agent::*;
use crate::command::Command;
use nightshade::ecs::audio::components::AudioSource;
use nightshade::ecs::camera::components::Camera;
use nightshade::ecs::decal::components::Decal;
use nightshade::ecs::event::Event;
use nightshade::ecs::gizmos::GizmoMode;
use nightshade::ecs::light::components::Light;
use nightshade::ecs::material::components::Material;
use nightshade::ecs::particles::components::ParticleEmitter;
use nightshade::ecs::scene::{SceneChunkConfig, SceneLayerConfig};
use nightshade::ecs::text::components::TextProperties;
pub use nightshade::prelude::{
Atmosphere, CharacterControllerComponent, ColliderComponent, NavMeshAgent, RigidBodyComponent,
};
use serde::{Deserialize, Serialize};
pub use nightshade::ecs::audio::components::AudioSource as AudioSourceComponent;
pub use nightshade::ecs::camera::components::{
Camera as CameraComponent, OrthographicCamera, PerspectiveCamera, Projection,
};
pub use nightshade::ecs::decal::components::Decal as DecalComponent;
pub use nightshade::ecs::gizmos::GizmoMode as GizmoModeKind;
pub use nightshade::ecs::light::components::{AreaLightShape, Light as LightComponent, LightType};
pub use nightshade::ecs::material::components::{AlphaMode, Material as MaterialComponent};
pub use nightshade::ecs::particles::components::ParticleEmitter as ParticleEmitterComponent;
pub use nightshade::ecs::physics::components::ColliderShape;
pub use nightshade::ecs::physics::types::RigidBodyType;
pub use nightshade::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
pub use nightshade::ecs::text::components::TextProperties as TextPropertiesComponent;
pub use nightshade::prelude::{Atmosphere as AtmosphereKind, Vec2, Vec3, Vec4};
pub const MESSAGE_KEY: &str = "message";
pub const CANVAS_KEY: &str = "canvas";
pub const BYTES_KEY: &str = "bytes";
pub const GLTF_KEY: &str = "gltf";
pub const RESOURCES_KEY: &str = "resources";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TouchPhase {
Started,
Moved,
Ended,
Cancelled,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "protocol-agent",
derive(enum2schema::Schema),
schema(string_enum)
)]
pub enum ShapeKind {
Cube,
Plane,
Cylinder,
Cone,
Sphere,
Torus,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "protocol-agent",
derive(enum2schema::Schema),
schema(string_enum)
)]
pub enum LightKind {
Point,
Spot,
Directional,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "protocol-agent",
derive(enum2schema::Schema),
schema(string_enum)
)]
pub enum GeneratorKind {
Building,
Room,
Tower,
Stairs,
Columns,
Perimeter,
CityBlock,
Courtyard,
Wfc,
WfcCity,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "protocol-agent",
derive(enum2schema::Schema),
schema(string_enum)
)]
pub enum PolyhavenCategory {
Hdris,
Models,
}
pub use crate::reflect::{ComponentKind, ComponentPatch};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum AnimationCommand {
Play { index: u32 },
PlayAll,
Pause,
Resume,
Stop,
Seek { time: f32 },
SetSpeed { speed: f32 },
SetLooping { looping: bool },
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
pub struct GenerationSettings {
pub footprint_x: f32,
pub footprint_z: f32,
pub stories: u32,
pub story_height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub window_spacing: f32,
pub seed: u32,
pub variation: f32,
}
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct RenderSettingsState {
pub ambient_light: [f32; 4],
pub bloom_enabled: bool,
pub bloom_intensity: f32,
pub bloom_threshold: f32,
pub bloom_knee: f32,
pub bloom_filter_radius: f32,
pub ssao_enabled: bool,
pub ssao_radius: f32,
pub ssao_intensity: f32,
pub ssao_bias: f32,
pub ssao_sample_count: u32,
pub ssao_visualization: bool,
pub ssgi_enabled: bool,
pub ssgi_radius: f32,
pub ssgi_intensity: f32,
pub ssgi_max_steps: u32,
pub ssr_enabled: bool,
pub ssr_max_steps: u32,
pub ssr_thickness: f32,
pub ssr_max_distance: f32,
pub ssr_stride: f32,
pub ssr_fade_start: f32,
pub ssr_fade_end: f32,
pub ssr_intensity: f32,
pub fog_enabled: bool,
pub fog_color: [f32; 3],
pub fog_start: f32,
pub fog_end: f32,
pub dof_enabled: bool,
pub dof_focus_distance: f32,
pub dof_focus_range: f32,
pub dof_max_blur_radius: f32,
pub dof_bokeh_threshold: f32,
pub dof_bokeh_intensity: f32,
pub dof_quality: u32,
pub fxaa_enabled: bool,
pub render_scale: f32,
pub ibl_blend_factor: f32,
pub pbr_debug_index: u32,
pub unlit_mode: bool,
pub selection_outline_enabled: bool,
pub selection_outline_color: [f32; 4],
pub exposure: f32,
pub saturation: f32,
pub contrast: f32,
pub brightness: f32,
pub gamma: f32,
pub show_grid: bool,
pub show_sky: bool,
pub show_normals: bool,
pub navmesh_debug: bool,
pub atmosphere: Atmosphere,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct EditorFlags {
pub gizmo_mode: GizmoMode,
pub snap_enabled: bool,
pub snap_translation_step: f32,
pub snap_rotation_step_degrees: f32,
pub snap_scale_step: f32,
pub greybox_enabled: bool,
pub greybox_show_final: bool,
pub placement_active: bool,
pub placement_shape: ShapeKind,
pub placement_size: [f32; 3],
pub placement_orient: bool,
pub placement_status: String,
pub push_pull_active: bool,
pub play_active: bool,
pub scripting_active: bool,
pub playground_active: bool,
pub fly_mode: bool,
pub viewer_mode: bool,
pub frame_on_load: bool,
pub day_night_hour: f32,
pub day_night_auto: bool,
pub skeleton_view: bool,
pub rotation_speed: f32,
pub active_palette: usize,
pub edit_mode_target: Option<u32>,
pub generation: GenerationSettings,
pub global_scripts: Vec<nightshade::ecs::script::components::GlobalScript>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
pub enum GlobalScriptCommand {
Add,
AddTemplate { name: String, source: String },
Remove(u32),
MoveUp(u32),
MoveDown(u32),
SetEnabled { index: u32, enabled: bool },
SetSource { index: u32, source: String },
Rename { index: u32, name: String },
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct TreeRow {
pub id: u32,
pub name: String,
pub depth: u32,
pub has_children: bool,
pub camera: bool,
pub light: bool,
pub mesh: bool,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct PaletteEntry {
pub label: String,
pub color: [f32; 3],
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct PrefabLink {
pub name: String,
pub source_path: Option<String>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct MorphMeshInfo {
pub id: u32,
pub name: String,
pub weights: Vec<f32>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct AnimationInfo {
pub clips: Vec<String>,
pub current: Option<u32>,
pub playing: bool,
pub time: f32,
pub duration: f32,
pub speed: f32,
pub looping: bool,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct SelectedEntity {
pub id: u32,
pub revision: u32,
pub name: String,
pub translation: [f32; 3],
pub rotation: [f32; 3],
pub scale: [f32; 3],
pub mesh: Option<String>,
pub material_name: Option<String>,
pub tags: Vec<String>,
pub layer: Option<u32>,
pub chunk: Option<u32>,
pub prefab: Option<PrefabLink>,
pub animation: Option<AnimationInfo>,
pub morph_weights: Option<Vec<f32>>,
pub selected_count: u32,
pub visibility: Option<bool>,
pub casts_shadow: bool,
pub light: Option<Light>,
pub camera: Option<Camera>,
pub is_active_camera: bool,
pub rigid_body: Option<RigidBodyComponent>,
pub collider: Option<ColliderComponent>,
pub character_controller: Option<CharacterControllerComponent>,
pub navmesh_agent: Option<NavMeshAgent>,
pub particle_emitter: Option<Box<ParticleEmitter>>,
pub decal: Option<Decal>,
pub audio_source: Option<AudioSource>,
pub render_layer: Option<RenderLayer>,
pub culling_mask: Option<CullingMask>,
pub camera_culling_mask: Option<CameraCullingMask>,
pub ignore_parent_scale: bool,
pub text: Option<(String, TextProperties)>,
pub script: Option<String>,
pub present: Vec<ComponentKind>,
pub addable: Vec<ComponentKind>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct MaterialEntry {
pub name: String,
pub material: Material,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct KhronosEntry {
pub label: String,
pub thumbnail: Option<String>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct PolyhavenEntry {
pub slug: String,
pub name: String,
pub thumbnail: String,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct SceneStructure {
pub layers: Vec<SceneLayerConfig>,
pub chunks: Vec<SceneChunkConfig>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
pub enum EditorAction {
ToggleGroundGrid,
ToggleSky,
ToggleLitUnlit,
ToggleViewerMode,
SetViewerMode(bool),
FrameScene,
ToggleFrameOnLoad,
ClearScene,
SnapSelectionToFloor,
BakeNavmesh,
ToggleNavmeshDebug,
SetAtmosphere(Atmosphere),
SetGizmoMode(GizmoMode),
RandomizeBoth,
SelectEntity(u32),
ToggleSelectEntity(u32),
ClearSelection,
AddEntityChild(u32),
ViewCamera(u32),
ToggleSnap,
ToggleSkeletonView,
SetMaterialVariant(Option<String>),
ToggleCameraGizmos,
ToggleShowNormals,
ToggleDayNightCycle,
SetDayNightHour(f32),
LoadKhronosByIndex(usize),
LoadPolyhavenByIndex {
category: PolyhavenCategory,
index: usize,
},
SetPolyhavenResolution(u32),
RefreshBrowsers,
DeepLink(String),
SpawnLines,
SpawnMeshes,
Spawn3DText,
SpawnTextLattice,
ClothSponza,
NewProject,
LoadExampleProject,
SaveProject,
ExportGlb,
SaveSelectedAsPrefab,
RefreshSelectedPrefab,
BreakSelectedPrefabLink,
SpawnPrefab(String),
Undo,
Redo,
DeleteEntity(u32),
DeleteSelectedEntity,
DuplicateEntity(u32),
DuplicateSelectedEntity,
AddShape(ShapeKind),
AddInstancedShape(ShapeKind),
ConvertSimilarToInstanced,
AddEmpty,
AddCamera,
AddPlayerSpawn,
AddSavePoint,
AddPatrolPoint,
AddTriggerVolume,
StampDecalAtCursor,
AddLight(LightKind),
AddTagToSelected(String),
RemoveTagFromSelected(String),
ToggleGreyboxMode,
ApplyGreyboxToSelection,
SnapSelectionToGrid,
MarkSelectionAsBrush,
ToggleShowFinal,
TogglePlay,
ToggleScripting,
SetPlay(bool),
SetScripting(bool),
SetPlayground(bool),
LoadLesson(String),
GlobalScript(GlobalScriptCommand),
TogglePushPull,
SetPlacementActive(bool),
SetPlacementShape(ShapeKind),
SetPlacementSize([f32; 3]),
SetOrientToNormal(bool),
RunGenerator(GeneratorKind),
SetGenerationSettings(GenerationSettings),
SetPaletteIndex(usize),
SetSnapEnabled(bool),
SetSnapSteps {
translation: f32,
rotation_degrees: f32,
scale: f32,
},
SetRotationSpeed(f32),
SetEntityLayer {
id: u32,
layer: Option<u32>,
},
SetEntityChunk {
id: u32,
chunk: Option<u32>,
},
AddSceneLayer,
RemoveSceneLayer(u32),
UpdateSceneLayer(SceneLayerConfig),
AddSceneChunk,
RemoveSceneChunk(u32),
UpdateSceneChunk(SceneChunkConfig),
}
#[derive(Clone, Serialize, Deserialize)]
pub struct CameraEntry {
pub id: u32,
pub name: String,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct ViewportTile {
pub camera: u32,
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
#[derive(Clone, Serialize, Deserialize)]
pub enum ClientMessage {
Init {
width: f32,
height: f32,
},
Resize {
width: f32,
height: f32,
},
SetViewportTiles(Vec<ViewportTile>),
PointerMove {
x: f32,
y: f32,
},
PointerMotion {
dx: f32,
dy: f32,
},
PointerButton {
button: u8,
pressed: bool,
},
Wheel {
delta: f32,
},
Touch {
id: u64,
phase: TouchPhase,
x: f32,
y: f32,
},
Key {
code: String,
pressed: bool,
text: Option<String>,
},
Action(Box<EditorAction>),
SetTransform {
id: u32,
translation: [f32; 3],
rotation: [f32; 3],
scale: [f32; 3],
committed: bool,
},
SetEntityName {
id: u32,
name: String,
},
SetComponent {
id: u32,
payload: Box<ComponentPatch>,
committed: bool,
},
AddComponent {
id: u32,
kind: ComponentKind,
},
RemoveComponent {
id: u32,
kind: ComponentKind,
},
Animation {
id: u32,
command: AnimationCommand,
},
UpdateMaterial {
name: String,
material: Box<Material>,
committed: bool,
},
SetRenderSettings(Box<RenderSettingsState>),
DropAsset {
name: String,
},
LoadGltfBundle {
name: String,
},
UpgradeBrush {
name: String,
},
SubmitCommand {
command: String,
},
#[cfg(feature = "protocol-agent")]
Agent(Box<AgentRequest>),
}
#[derive(Clone, Serialize, Deserialize)]
pub enum WorkerMessage {
Ready {
adapter: String,
},
Stats {
fps: f32,
entity_count: u32,
},
Camera {
right: [f32; 3],
up: [f32; 3],
forward: [f32; 3],
},
Scene {
rows: Vec<TreeRow>,
},
Selected {
detail: Option<Box<SelectedEntity>>,
},
Cameras {
entries: Vec<CameraEntry>,
},
Animation {
time: f32,
duration: f32,
playing: bool,
clip: Option<u32>,
},
MorphWeights {
id: u32,
weights: Vec<f32>,
},
MorphMeshes {
entries: Vec<MorphMeshInfo>,
},
MaterialVariants {
names: Vec<String>,
active: Option<String>,
},
RenderSettings(Box<RenderSettingsState>),
Flags(Box<EditorFlags>),
Materials {
entries: Vec<MaterialEntry>,
},
Tags {
entries: Vec<(String, u32)>,
},
Structure(SceneStructure),
Palette {
entries: Vec<PaletteEntry>,
},
KhronosList {
entries: Vec<KhronosEntry>,
},
PolyhavenList {
category: PolyhavenCategory,
entries: Vec<PolyhavenEntry>,
},
Loading {
active: bool,
label: String,
},
Project {
name: String,
modified: bool,
model: Option<String>,
mode: String,
},
SaveFile {
name: String,
},
PointerLock {
locked: bool,
},
FocusEntity {
id: u32,
},
UndoState {
can_undo: bool,
can_redo: bool,
},
Toast {
message: String,
error: bool,
},
CommandLog {
entries: Vec<CommandLogEntry>,
},
#[cfg(feature = "protocol-agent")]
Agent(Box<AgentResponse>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CommandLogKind {
Command,
Event,
Error,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommandLogEntry {
pub cycle: u64,
pub kind: CommandLogKind,
pub label: String,
pub detail: String,
pub entities: Vec<u32>,
}
impl CommandLogEntry {
pub fn from_event(cycle: u64, event: &Event) -> Self {
Self {
cycle,
kind: CommandLogKind::Event,
label: event_label(event).to_string(),
detail: event_detail(event),
entities: event_entities(event),
}
}
pub fn from_command(cycle: u64, command: &Command) -> Self {
use enum2schema::serde_json::Value;
let value: Value = enum2schema::serde_json::to_value(command).unwrap_or(Value::Null);
let detail = value
.as_object()
.and_then(|object| object.values().next())
.map(|inner| inner.to_string())
.unwrap_or_else(|| value.to_string());
let mut entities = Vec::new();
collect_entity_ids(&value, &mut entities);
Self {
cycle,
kind: CommandLogKind::Command,
label: command.name().to_string(),
detail,
entities,
}
}
pub fn from_error(cycle: u64, message: String) -> Self {
Self {
cycle,
kind: CommandLogKind::Error,
label: "error".to_string(),
detail: message,
entities: Vec::new(),
}
}
}
fn event_label(event: &Event) -> &'static str {
match event {
Event::Collision { .. } => "Collision",
Event::Despawned { .. } => "Despawned",
Event::AnimationFinished { .. } => "AnimationFinished",
Event::AnimationEvent { .. } => "AnimationEvent",
Event::NavigationArrived { .. } => "NavigationArrived",
}
}
fn event_detail(event: &Event) -> String {
match event {
Event::Collision {
a,
b,
sensor,
started,
} => format!(
"#{} and #{}, {}{}",
a.id,
b.id,
if *started { "started" } else { "ended" },
if *sensor { ", sensor" } else { "" }
),
Event::AnimationEvent { entity, name } => format!("#{} {}", entity.id, name),
Event::Despawned { entity }
| Event::AnimationFinished { entity }
| Event::NavigationArrived { entity } => format!("#{}", entity.id),
}
}
fn event_entities(event: &Event) -> Vec<u32> {
match event {
Event::Collision { a, b, .. } => vec![a.id, b.id],
Event::AnimationEvent { entity, .. } => vec![entity.id],
Event::Despawned { entity }
| Event::AnimationFinished { entity }
| Event::NavigationArrived { entity } => vec![entity.id],
}
}
fn collect_entity_ids(value: &enum2schema::serde_json::Value, ids: &mut Vec<u32>) {
use enum2schema::serde_json::Value;
match value {
Value::Object(map) => {
if let Some(id) = map.get("Existing").and_then(Value::as_u64) {
ids.push(id as u32);
}
if let Some(id) = map
.get("Entity")
.and_then(Value::as_object)
.and_then(|entity| entity.get("id"))
.and_then(Value::as_u64)
{
ids.push(id as u32);
}
for nested in map.values() {
collect_entity_ids(nested, ids);
}
}
Value::Array(array) => {
for nested in array {
collect_entity_ids(nested, ids);
}
}
_ => {}
}
}
#[cfg(all(test, feature = "protocol-agent"))]
mod schema_tests {
use super::EditorAction;
use enum2schema::Schema;
use enum2schema::serde_json::Value;
fn assert_no_array_items(value: &Value) {
match value {
Value::Object(object) => {
if let Some(items) = object.get("items") {
assert!(
!items.is_array(),
"array-valued `items` is invalid in draft 2020-12, use `prefixItems`: {object:?}"
);
}
for nested in object.values() {
assert_no_array_items(nested);
}
}
Value::Array(array) => {
for nested in array {
assert_no_array_items(nested);
}
}
_ => {}
}
}
#[test]
fn editor_action_schema_is_draft_2020_12() {
assert_no_array_items(&EditorAction::schema());
}
}