feat: implement audio! thats all nodes!
Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
66
Cargo.lock
generated
66
Cargo.lock
generated
@@ -2656,6 +2656,15 @@ dependencies = [
|
|||||||
"syn 2.0.103",
|
"syn 2.0.103",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs"
|
||||||
|
version = "0.8.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -3367,6 +3376,12 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hound"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@@ -5308,7 +5323,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1"
|
checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cpal",
|
"cpal",
|
||||||
|
"hound",
|
||||||
"lewton",
|
"lewton",
|
||||||
|
"symphonia",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5937,6 +5954,55 @@ dependencies = [
|
|||||||
"zeno",
|
"zeno",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symphonia"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"symphonia-bundle-mp3",
|
||||||
|
"symphonia-core",
|
||||||
|
"symphonia-metadata",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symphonia-bundle-mp3"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"symphonia-core",
|
||||||
|
"symphonia-metadata",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symphonia-core"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"bytemuck",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symphonia-metadata"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
|
||||||
|
dependencies = [
|
||||||
|
"encoding_rs",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"symphonia-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ mint = "0.5.9"
|
|||||||
tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
|
tokio = { version = "1.39.2", features = ["rt-multi-thread", "signal", "time"] }
|
||||||
|
|
||||||
# bevy
|
# bevy
|
||||||
bevy = { version = "0.16", features = ["wayland", "bevy_remote"] }
|
bevy = { version = "0.16", features = ["wayland", "bevy_remote", "mp3", "wav"] }
|
||||||
bevy_mod_xr = "0.3"
|
bevy_mod_xr = "0.3"
|
||||||
bevy_mod_openxr = "0.3"
|
bevy_mod_openxr = "0.3"
|
||||||
# bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext"
|
# bevy_mod_meshtext.git = "https://github.com/Schmarni-Dev/bevy_mod_meshtext"
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ use bevy_mod_xr::camera::XrProjection;
|
|||||||
use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin;
|
use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin;
|
||||||
use bevy_mod_xr::session::{XrFirst, XrHandleEvents};
|
use bevy_mod_xr::session::{XrFirst, XrHandleEvents};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use nodes::audio::AudioNodePlugin;
|
||||||
use core::client::{Client, tick_internal_client};
|
use core::client::{Client, tick_internal_client};
|
||||||
use core::entity_handle::EntityHandlePlugin;
|
use core::entity_handle::EntityHandlePlugin;
|
||||||
use core::task;
|
use core::task;
|
||||||
@@ -394,6 +395,7 @@ fn bevy_loop(
|
|||||||
ModelNodePlugin,
|
ModelNodePlugin,
|
||||||
TextNodePlugin,
|
TextNodePlugin,
|
||||||
LinesNodePlugin,
|
LinesNodePlugin,
|
||||||
|
AudioNodePlugin,
|
||||||
));
|
));
|
||||||
// object plugins
|
// object plugins
|
||||||
app.add_plugins((PlaySpacePlugin, HandPlugin, ControllerPlugin));
|
app.add_plugins((PlaySpacePlugin, HandPlugin, ControllerPlugin));
|
||||||
@@ -576,7 +578,7 @@ fn stereokit_loop(
|
|||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
wayland.update();
|
wayland.update();
|
||||||
drawable::draw(token);
|
drawable::draw(token);
|
||||||
audio::update();
|
// audio::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Cleanly shut down StereoKit");
|
info!("Cleanly shut down StereoKit");
|
||||||
|
|||||||
@@ -1,26 +1,93 @@
|
|||||||
|
use super::spatial::SpatialNode;
|
||||||
use super::{Aspect, AspectIdentifier, Node};
|
use super::{Aspect, AspectIdentifier, Node};
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::destroy_queue;
|
use crate::core::entity_handle::EntityHandle;
|
||||||
use crate::core::error::Result;
|
use crate::core::error::Result;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::core::resource::get_resource_file;
|
use crate::core::resource::get_resource_file;
|
||||||
use crate::nodes::spatial::{SPATIAL_ASPECT_ALIAS_INFO, Spatial, Transform};
|
use crate::nodes::spatial::{SPATIAL_ASPECT_ALIAS_INFO, Spatial, Transform};
|
||||||
|
use bevy::audio::{PlaybackMode, Volume};
|
||||||
|
use bevy_mod_openxr::session::OxrSession;
|
||||||
|
use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated};
|
||||||
|
use bevy_mod_xr::spaces::XrSpace;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use glam::{Vec4Swizzles, vec3};
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use stardust_xr::values::ResourceID;
|
use stardust_xr::values::ResourceID;
|
||||||
|
|
||||||
use std::ops::DerefMut;
|
use bevy::prelude::*;
|
||||||
|
use bevy::transform::components::Transform as BevyTransform;
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
use std::{ffi::OsStr, path::PathBuf};
|
use std::{ffi::OsStr, path::PathBuf};
|
||||||
use stereokit_rust::sound::{Sound as SkSound, SoundInst};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
pub struct AudioNodePlugin;
|
pub struct AudioNodePlugin;
|
||||||
impl Plugin for AudioNodePlugin {
|
impl Plugin for AudioNodePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
todo!()
|
app.add_systems(Update, update_sound_event);
|
||||||
}
|
app.add_systems(XrSessionCreated, spawn_hmd_audio_listener);
|
||||||
|
app.add_systems(XrPreDestroySession, despawn_hmd_audio_listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn despawn_hmd_audio_listener(mut cmds: Commands, session: Res<OxrSession>, res: Res<HmdListener>) {
|
||||||
|
cmds.remove_resource::<HmdListener>();
|
||||||
|
cmds.entity(res.0).despawn();
|
||||||
|
_ = session.destroy_space(res.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_hmd_audio_listener(mut cmds: Commands, session: Res<OxrSession>) {
|
||||||
|
let space = session
|
||||||
|
.create_reference_space(openxr::ReferenceSpaceType::VIEW, BevyTransform::IDENTITY)
|
||||||
|
.unwrap();
|
||||||
|
let listener = cmds
|
||||||
|
.spawn((
|
||||||
|
Name::new("HMD audio listener"),
|
||||||
|
space.0,
|
||||||
|
SpatialListener::new(0.2),
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
cmds.insert_resource(HmdListener(listener, space.0));
|
||||||
|
}
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct HmdListener(Entity, XrSpace);
|
||||||
|
fn update_sound_event(
|
||||||
|
mut cmds: Commands,
|
||||||
|
sinks: Query<&SpatialAudioSink>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
for sound in SOUND_REGISTRY.get_valid_contents() {
|
||||||
|
if sound.entity.get().is_none() {
|
||||||
|
let handle = asset_server.load(sound.pending_audio_path.as_path());
|
||||||
|
sound
|
||||||
|
.entity
|
||||||
|
.set(
|
||||||
|
cmds.spawn((
|
||||||
|
Name::new("Audio Node"),
|
||||||
|
SpatialNode(Arc::downgrade(&sound.spatial)),
|
||||||
|
AudioPlayer::new(handle),
|
||||||
|
PlaybackSettings {
|
||||||
|
mode: PlaybackMode::Once,
|
||||||
|
volume: Volume::Linear(sound.volume),
|
||||||
|
speed: 1.0,
|
||||||
|
paused: true,
|
||||||
|
muted: false,
|
||||||
|
spatial: true,
|
||||||
|
spatial_scale: None,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.id()
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.0).ok()) {
|
||||||
|
if sound.play.lock().take().is_some() {
|
||||||
|
sink.play();
|
||||||
|
}
|
||||||
|
if sound.stop.lock().take().is_some() {
|
||||||
|
sink.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
static SOUND_REGISTRY: Registry<Sound> = Registry::new();
|
||||||
@@ -31,8 +98,7 @@ pub struct Sound {
|
|||||||
|
|
||||||
volume: f32,
|
volume: f32,
|
||||||
pending_audio_path: PathBuf,
|
pending_audio_path: PathBuf,
|
||||||
sk_sound: OnceLock<SkSound>,
|
entity: OnceLock<EntityHandle>,
|
||||||
instance: Mutex<Option<SoundInst>>,
|
|
||||||
stop: Mutex<Option<()>>,
|
stop: Mutex<Option<()>>,
|
||||||
play: Mutex<Option<()>>,
|
play: Mutex<Option<()>>,
|
||||||
}
|
}
|
||||||
@@ -48,8 +114,7 @@ impl Sound {
|
|||||||
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
|
spatial: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
volume: 1.0,
|
volume: 1.0,
|
||||||
pending_audio_path,
|
pending_audio_path,
|
||||||
sk_sound: OnceLock::new(),
|
entity: OnceLock::new(),
|
||||||
instance: Mutex::new(None),
|
|
||||||
stop: Mutex::new(None),
|
stop: Mutex::new(None),
|
||||||
play: Mutex::new(None),
|
play: Mutex::new(None),
|
||||||
};
|
};
|
||||||
@@ -57,24 +122,6 @@ impl Sound {
|
|||||||
node.add_aspect_raw(sound_arc.clone());
|
node.add_aspect_raw(sound_arc.clone());
|
||||||
Ok(sound_arc)
|
Ok(sound_arc)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self) {
|
|
||||||
let sound = self
|
|
||||||
.sk_sound
|
|
||||||
.get_or_init(|| SkSound::from_file(self.pending_audio_path.clone()).unwrap());
|
|
||||||
if self.stop.lock().take().is_some() {
|
|
||||||
if let Some(instance) = self.instance.lock().take() {
|
|
||||||
instance.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.instance.lock().is_none() && self.play.lock().take().is_some() {
|
|
||||||
let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume));
|
|
||||||
self.instance.lock().replace(instance);
|
|
||||||
}
|
|
||||||
if let Some(instance) = self.instance.lock().deref_mut() {
|
|
||||||
instance.position(self.spatial.global_transform().w_axis.xyz());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl AspectIdentifier for Sound {
|
impl AspectIdentifier for Sound {
|
||||||
impl_aspect_for_sound_aspect_id! {}
|
impl_aspect_for_sound_aspect_id! {}
|
||||||
@@ -96,22 +143,10 @@ impl SoundAspect for Sound {
|
|||||||
}
|
}
|
||||||
impl Drop for Sound {
|
impl Drop for Sound {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(instance) = self.instance.lock().take() {
|
|
||||||
instance.stop();
|
|
||||||
}
|
|
||||||
if let Some(sk_sound) = self.sk_sound.take() {
|
|
||||||
destroy_queue::add(sk_sound);
|
|
||||||
}
|
|
||||||
SOUND_REGISTRY.remove(self);
|
SOUND_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update() {
|
|
||||||
for sound in SOUND_REGISTRY.get_valid_contents() {
|
|
||||||
sound.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterfaceAspect for Interface {
|
impl InterfaceAspect for Interface {
|
||||||
#[doc = "Create a sound node. WAV and MP3 are supported."]
|
#[doc = "Create a sound node. WAV and MP3 are supported."]
|
||||||
fn create_sound(
|
fn create_sound(
|
||||||
|
|||||||
@@ -155,7 +155,10 @@ fn build_line_mesh(
|
|||||||
match lines.entity.get() {
|
match lines.entity.get() {
|
||||||
Some(e) => cmds.entity(**e),
|
Some(e) => cmds.entity(**e),
|
||||||
None => {
|
None => {
|
||||||
let e = cmds.spawn(SpatialNode(Arc::downgrade(&lines.spatial)));
|
let e = cmds.spawn((
|
||||||
|
Name::new("LinesNode"),
|
||||||
|
SpatialNode(Arc::downgrade(&lines.spatial)),
|
||||||
|
));
|
||||||
_ = lines.entity.set(e.id().into());
|
_ = lines.entity.set(e.id().into());
|
||||||
e
|
e
|
||||||
}
|
}
|
||||||
@@ -223,7 +226,6 @@ impl Lines {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("line::add_to");
|
|
||||||
let lines = LINES_REGISTRY.add(Lines {
|
let lines = LINES_REGISTRY.add(Lines {
|
||||||
spatial: node.get_aspect::<Spatial>()?.clone(),
|
spatial: node.get_aspect::<Spatial>()?.clone(),
|
||||||
data: Mutex::new(lines),
|
data: Mutex::new(lines),
|
||||||
@@ -238,7 +240,6 @@ impl Lines {
|
|||||||
}
|
}
|
||||||
impl LinesAspect for Lines {
|
impl LinesAspect for Lines {
|
||||||
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
|
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
|
||||||
info!("set_lines");
|
|
||||||
let lines_aspect = node.get_aspect::<Lines>()?;
|
let lines_aspect = node.get_aspect::<Lines>()?;
|
||||||
*lines_aspect.data.lock() = lines;
|
*lines_aspect.data.lock() = lines;
|
||||||
lines_aspect.gen_mesh.store(true, Ordering::Relaxed);
|
lines_aspect.gen_mesh.store(true, Ordering::Relaxed);
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ fn load_models(
|
|||||||
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path));
|
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path));
|
||||||
let entity = cmds
|
let entity = cmds
|
||||||
.spawn((
|
.spawn((
|
||||||
|
Name::new("ModelNode"),
|
||||||
SceneRoot(handle),
|
SceneRoot(handle),
|
||||||
ModelNode(Arc::downgrade(&model)),
|
ModelNode(Arc::downgrade(&model)),
|
||||||
SpatialNode(Arc::downgrade(&model.spatial)),
|
SpatialNode(Arc::downgrade(&model.spatial)),
|
||||||
@@ -394,7 +395,6 @@ impl MaterialParameter {
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
info!(texture_param = parameter_name, path = ?texture_path);
|
|
||||||
let handle = asset_server.load(texture_path);
|
let handle = asset_server.load(texture_path);
|
||||||
match parameter_name {
|
match parameter_name {
|
||||||
"diffuse" => mat.diffuse_texture = Some(handle),
|
"diffuse" => mat.diffuse_texture = Some(handle),
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{
|
core::{
|
||||||
bevy_channel::{BevyChannel, BevyChannelReader}, client::Client, color::ColorConvert, entity_handle::EntityHandle, error::Result, registry::Registry, resource::get_resource_file
|
bevy_channel::{BevyChannel, BevyChannelReader},
|
||||||
|
client::Client,
|
||||||
|
color::ColorConvert,
|
||||||
|
entity_handle::EntityHandle,
|
||||||
|
error::Result,
|
||||||
|
registry::Registry,
|
||||||
|
resource::get_resource_file,
|
||||||
},
|
},
|
||||||
nodes::{
|
nodes::{
|
||||||
drawable::XAlign, spatial::{Spatial, SpatialNode}, Node
|
Node,
|
||||||
|
drawable::XAlign,
|
||||||
|
spatial::{Spatial, SpatialNode},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy::{platform::collections::HashMap, prelude::*};
|
use bevy::{platform::collections::HashMap, prelude::*};
|
||||||
@@ -95,9 +103,8 @@ fn spawn_text(
|
|||||||
);
|
);
|
||||||
let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
|
let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
|
||||||
let max_height = style.bounds.as_ref().map(|v| v.bounds.x);
|
let max_height = style.bounds.as_ref().map(|v| v.bounds.x);
|
||||||
let (width, height) =
|
let (width, _height) =
|
||||||
text_glyphs.measure(max_width, max_height, &mut font_settings.font_system);
|
text_glyphs.measure(max_width, max_height, &mut font_settings.font_system);
|
||||||
info!(width, height, ?style.text_align_x);
|
|
||||||
let meshes = generate_meshes(
|
let meshes = generate_meshes(
|
||||||
bevy_mesh_text_3d::InputText::Simple {
|
bevy_mesh_text_3d::InputText::Simple {
|
||||||
text: text_string,
|
text: text_string,
|
||||||
@@ -134,9 +141,9 @@ fn spawn_text(
|
|||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let dist = meshes.iter().fold(f32::MAX, |dist, v| {
|
let dist = meshes
|
||||||
dist.min(v.transform.translation.x)
|
.iter()
|
||||||
});
|
.fold(f32::MAX, |dist, v| dist.min(v.transform.translation.x));
|
||||||
// TODO: text align
|
// TODO: text align
|
||||||
let letters = meshes
|
let letters = meshes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -162,7 +169,10 @@ fn spawn_text(
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let entity = cmds
|
let entity = cmds
|
||||||
.spawn((SpatialNode(Arc::downgrade(&text.spatial)),))
|
.spawn((
|
||||||
|
Name::new("TextNode"),
|
||||||
|
SpatialNode(Arc::downgrade(&text.spatial)),
|
||||||
|
))
|
||||||
.add_children(&letters)
|
.add_children(&letters)
|
||||||
.id();
|
.id();
|
||||||
text.entity.lock().replace(EntityHandle(entity));
|
text.entity.lock().replace(EntityHandle(entity));
|
||||||
|
|||||||
Reference in New Issue
Block a user