Expand description
§aframe-rs
This is an Aframe library for rust. It’s still fairly experimental and a lot might change. I started writing this for a bit of fun to see if I could play with aframe from inside a yew app. It started getting pretty large so I decided to abstract away all the yew-specific stuff and start making a library on its own. There’s still a bunch missing and a bunch to do here, but what IS there is functional.
§Setup
§Initialization
This crate contains an init
feature which may be enabled to allow initialization from an async function:
async fn app_main() -> Result<(), aframe::InitError>
{
aframe::init_aframe().await?;
// ... Now you can safely continue
}
You can also initialize simply by adding the Aframe script to your HTML header:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
§Use
You can either use this crate’s Htmlify
trait to output raw html, or use the yew-support
feature to create a yew componment (described lower in this readme) to output your actual Aframe scene.
§API
§Scene
Instantiating a scene:
scene!
§Components
Defining a new component:
component_def!
Declaring the structure of a defined component:
component_struct!
simple_enum!
complex_enum!
Instantiating a component struct:
component!
See the component module for more information and for pre-defined component structs.
§Custom Geometry
Defining a new custom geometry:
geometry_def!
Not yet implemented:
geometry_struct!
macro to declare structure of custom geometry datageometry!
macro to serve as a helper when instantiating custom geometry
§Entities & Primitives
Instantiating an entity or defined primitive:
entity!
Defining a new primitive:
primitive!
§Assets
The assets!
and mixin!
macros are provided to define an Assets
struct.
§Systems
§Shaders
§Htmlify
The Htmlify
trait is is to generate HTML from the structures provided in this crate. This was abstracted into a separate crate:
You can use this to plop a Scene directly into your DOM with the web_sys crate, making this crate usable even without any supporting framework:
// Say we have some `Scene` structure already constructed:
let body = web_sys::window()?.document()?.body()?;
body.append_with_node_1(scene.as_element()?.as_ref())?;
// Or even simpler:
htmlify::append_to_document_body(&scene);
Here’s a basic example of a fully-functional page being created using wasm-bindgen-test
:
test example
§Sys API
The lowest-level calls to Aframe are defined in the sys
module:
registerPrimitive
registerComponent
registerSystem
registerShader
registerGeometry
registerElement
§yew_support feature
The yew_support
feature adds yew support to this crate. At its core, all this does is implement From<&Scene> for Html
along with a few other conversions to yew’s Html type.
See the yew-ext module page for an example.
§WIP/Missing Features
- Event handling
- State handling
- High-level support for custom geometry
- Access to Aframe utility functions
- Some component implementations are still accepting strings where they could accept enums or more specific structures
§Example
Below is a full example of how a scene is constructed in yew (this also serves of a valid example of how to use the scene!
macro even outside of a yew context):
html!
{
<Aframe scene =
{
// Using this contant to clean up some fluff in the code below.
const CURSOR_COLOR: [(Cow<'static, str>, Cow<'static, str>); 1] =
[(Cow::Borrowed("color"), Cow::Borrowed("lightblue"))];
scene!
{
// TODO: Some of these attributes are actually components, they need to be implemented in the library!
attributes: ("inspector", "true"), ("embedded", "true"), ("cursor", "rayOrigin: mouse"),
("mixin", "intersect_ray"), ("style", "min-height: 50px;"),
assets: assets!
{
// Assume we have a few assets available to use
Image::new("ramen", "/pics/ramen.png"),
Image::new("noise", "/pics/noise.bmp"),
// Create a mixin for shadows to know what to interact with
mixin!
{
"intersect_ray",
("raycaster", component!
{
RayCaster,
objects: List(Cow::Borrowed(&[Cow::Borrowed("#ramen-cube, #water")]))
})
}
},
children:
// The camera rig
entity!
{
attributes: ("id", "rig"),
components:
("position", component::Position { x: 0.0, y: 0.0, z: 0.0 }),
("geometry", component!
{
component::Geometry,
primitive: component::GeometryPrimitive::Ring
{
radius_inner: 0.06,
radius_outer: 0.2,
segments_theta: 32,
segments_phi: 8,
theta_start: 0.0,
theta_length: 360.0
}
}),
("material", component!
{
component::Material,
props: component::MaterialProps(Cow::Borrowed(&CURSOR_COLOR)),
opacity: 0.8
}),
children:
// The camera
entity!
{
attributes: ("id", "camera"),
components:
("position", component::Position { x: 0.0, y: 1.8, z: 0.0 }),
("camera", component!(component::Camera)),
("look-controls", component!(component::LookControls))
},
},
entity!
{
attributes: ("id", "cube-rig"),
components:
("position", component::Position{x: 0.0, y: 2.5, z: -2.0}),
("sound", component!
{
component::Sound,
src: Cow::Borrowed("#ambient_music"),
volume: 0.5
}),
("light", component!
{
component::Light,
light_type: component::LightType::Point
{
decay: 1.0,
distance: 50.0,
shadow: component::OptionalLocalShadow::NoCast{},
},
intensity: 0.0
}),
("animation__mouseenter", component!
{
component::Animation,
property: Cow::Borrowed("light.intensity"),
to: Cow::Borrowed("1.0"),
start_events: component::List(Cow::Borrowed(&[Cow::Borrowed("mouseenter")])),
dur: 250
}),
("animation__mouseleave", component!
{
component::Animation,
property: Cow::Borrowed("light.intensity"),
to: Cow::Borrowed("0.0"),
start_events: component::List(Cow::Borrowed(&[Cow::Borrowed("mouseleave")])),
dur: 250
}),
// This assumes the existence of a primitive registered as "ramen-cube"
children: entity!
{
primitive: "ramen-cube",
attributes: ("id", "ramen-cube"),
components: // None
}
},
// Ambient light
entity!
{
attributes: ("id", "ambient-light"),
components: ("light", component!
{
component::Light,
light_type: component::LightType::Ambient{},
color: color::GREY73,
intensity: 0.2
})
},
// Directional light
entity!
{
attributes: ("id", "directional-light"),
components:
("position", component::Position{ x: 0.5, y: 1.0, z: 1.0 }),
("light", component!
{
component::Light,
light_type: component::LightType::Directional
{
shadow: component::OptionalDirectionalShadow::Cast
{
shadow: component!
{
component::DirectionalShadow
}
}
},
color: color::WHITE,
intensity: 0.1
})
},
// The sky
entity!
{
primitive: "a-sky",
attributes: ("id", "sky"),
components: ("material", component!
{
component::Material,
// This assumes the existence of a shader registered as "strobe"
shader: Cow::Borrowed("strobe"),
props: component::MaterialProps(Cow::Owned(vec!
(
(Cow::Borrowed("color"), Cow::Borrowed("black")),
(Cow::Borrowed("color2"), Cow::Borrowed("#222222"))
)))
})
},
// The ocean
entity!
{
primitive: "a-ocean",
attributes: ("id", "water"), ("depth", "100"), ("width", "100"), ("amplitude", "0.5"),
components: ("material", component!
{
component::Material,
// This assumes the existence of a shader registered as "water"
shader: Cow::Borrowed("water"),
props: component::MaterialProps(Cow::Owned(vec!((Cow::Borrowed("transparent"), Cow::Borrowed("true")))))
})
}
}
} />
}
Re-exports§
pub use shader::*;
pub use component::*;
pub use utils::*;
pub use entity::*;
pub use scene::*;
pub use assets::*;
pub use system::*;
Modules§
- Module for instantion of assets and mixins.
- Module dealing with components in all forms, including registration, representation, and instantiation.
- Module for the instantiaion of entities and primitives.
- The scene construct, the top-level container for all other Aframe structures.
- Module to provide tools for shader registration as well as built-in shader constants.
- Lower level FFI stuff. Mostly used internally, but exposed in case the abstractions of this library are too restrictive. Using this should not be necessary for the usage of this crate, but the public APIs have been provided while this crate is still feature-incomplete.
- The
js!
macro, vectors and other useful utility modules. - Support for yew. See the below example:
Macros§
- Constructs an Assets object for use in a scene. Example:
- Mid-level macro to create a vector of attributes
- A macro to define an enum in which each variant maps to an arbitrary number of fields which will themselves be flattened into fields of the component itself. Works similarly to the
component_struct!
macro. - A macro to instantiate a component. Mimics struct creation syntax, but allows any number of fields to be left out (in which case defaults will be used). Note that the ability to leave out fields does not extend to struct_like enum variants created in this macro. For example:
component!{component::Camera}
will create acamera
component with all its fields set to default values, whereascomponent!{component::Camera, active = false}
will create acamera
component with all its fields set to default values except theactive
field. - Top-level macro to define components. Usage resembles struct creation syntax. The
js!
macro is available for writing inline javascript, and returns a js_sys::Function object. This macro callsinto
on expressions passed into the fields expecting function, allowing thejs!
macro to be used as a catch-all. Takes the optional fields described in the table below. - While
component_def!
creates a component that Aframe can access from its own runtime, thecomponent_struct!
macro creates a Rust struct that mimics the internal details of that Aframe component. Component structs are already provided for Aframe’s built-in components (WIP: not all components are defined yet. Once all aframe components are defined, callingcomponent_struct!
should only be necessary for locally-defined components. Once all components from Aframe have been defined,component_def!
andcomponent_struct!
may be merged into a single macro to do the heavy-lifting of both at once). The component must be already registered in aframe before this struct may be used (although constructing it before that is safe). There are 2 variation of syntax provided, depending on the desired resultingDisplay
implementation. - Mid-level macro to create a vector of components
- Defines the high-level API for describing entities, with one form for describing general entities and another for defining specific primitives. Here’s an example of a general entity definition:
- Top-level macro to define custom geometries. Syntax resemles but is simpler than the
component_def!
macro. Thejs!
macro is available for writing inline javascript, and returns a js_sys::Function object. This macro callsinto
on expressions passed into the fields expecting function, allowing thejs!
macro to be used as a catch-all. Takes the optional fields described in the table below. - Allows a javascript function to be defined inline. Accepts 2 forms of syntax:
js!(<js code>);
js!(arg1, arg2, arg3 =>> <js code>)
There are some limitations: - Top-level macro to define a new primitive.
- Provided to define a
Scene
struct. - Defines an enum in which each variant maps to a single string (via a
Display
implementation). This can be combined withcomponent_def!
to crate fields with a limited number of possiblities. - Top-level macro to define systems. Usage resembles struct creation syntax. The
js!
macro is available for writing inline javascript, and returns a js_sys::Function object. This macro callsinto
on expressions passed into the fields expecting function, allowing thejs!
macro to be used as a catch-all. Takes the optional fields described in the table below.
Structs§
Functions§
- Async function which initializes aframe by adding the aframe script tag to the document header and waiting for the script onload event. Current Aframe version: 1.2.0