Crate aframe[][src]

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

Currently, this crate doesn’t contain any features to initialize Aframe itself, so in your HTML header you ought to include:

<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>

Beyond that, 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 constants.

Entities & Primitives

Instantiating an entity or defined primitive entity!

Defining a new primitive: primitive!

Shaders

Shader

Htmlify

The Htmlify trait is is to generate raw HTML from the structures provided in this crate. This may eventually be abstracted into a separate crate. (TBD: Is there a better crate in existence already?)

Htmlify

Assets

The assets! and mixin! macros are provided to define an Assets struct. Their signatures are as follows:

assets!
mixin!

Sys API

The lowest-level calls to Aframe are defined in the sys module:

registerPrimitive registerComponent registerShader

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. This allows you to write a yew component as such:

static INIT: AtomicBool = AtomicBool::new(false);

#[derive(Clone, PartialEq, Properties)]
pub struct AframeProps
{
    scene: aframe::Scene
}

pub struct Aframe
{
    props: AframeProps
}

impl crate::utils::Component for Aframe 
{
    type Message = Msg;
    type Properties = AframeProps;

    fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self 
    {
        // Register aframe stuff first time only
        if !INIT.load(Ordering::Relaxed)
        {
            unsafe 
            {
                // Code in this block registers shaders, components, and primitives with aframe
                shaders::register_shaders(); 
                component::register_components();
                primitive::register_primitives();
            }
            INIT.store(true, Ordering::Relaxed)
        }
        Self 
        { 
            props
        }
    }

    fn update(&mut self, _: Self::Message) -> ShouldRender 
    {
        true
    }

    fn change(&mut self, _props: Self::Properties) -> ShouldRender 
    {
        false
    }

    fn view(&self) -> Html 
    {
        (&self.props.scene).into()
    }
}

Below is a full definition of how the scene is defined 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::*;

Modules

High-level API

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.

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 a camera component with all its fields set to default values, whereas component!{component::Camera, active = false} will create a camera component with all its fields set to default values except the active 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 calls into on expressions passed into the fields expecting function, allowing the js! 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, the component_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, calling component_struct! should only be necessary for locally-defined components. Once all components from Aframe have been defined, component_def! and component_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 resulting Display 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:

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:

Constructs an AssetItem::Mixin foir use in an Assets struct. See the assets! macro for an example/

Top-level macro to define a new primitive.

Provided to define a Scene struct. See the top-level documentation for an example use.

Defines an enum in which each variant maps to a single string (via a Display implementation). This can be combined with component_def! to crate fields with a limited number of possiblities.