jabuwu / rusty_spine

Spine runtime for Rust (and wasm!) transpiled from the official C Runtime.
https://docs.rs/rusty_spine
Other
43 stars 4 forks source link

Add a capability for creating attachments. #19

Open brandon-reinhart opened 11 months ago

brandon-reinhart commented 11 months ago

Sometimes it is useful to create attachments programmatically. If a spine has many possible attachments, or if those attachments have unusual properties. A concrete example would be wanting to add an attachment to a slot that uses a different texture atlas than the one the skeleton is exported with.

There are two levels of this work:

  1. Creating and adding attachments that do not originate in the skeleton file.
  2. Providing some API for creating custom attachment loaders (for example, the user could provide a custom region attachment loader that loads from a single image file).
brandon-reinhart commented 11 months ago

I looked at the code, but at the moment this work is probably a bit beyond me. I don't have experience with writing unsafe rust that interfaces with a C library.

There are also some spine-c concepts I don't yet understand, like Sequence. Still reading the code..

My concrete goal is to allow for weapon and gear attachments that aren't specified by the spine project, so that a mod author could create a new weapon. These attachments would load from an atlas other than the one used by the skel file, but would attach to a slot specified in the spine project.

brandon-reinhart commented 11 months ago

This hacky test shows progress:

    let attachment = unsafe {
        let atlas_attachment_loader = c::spAtlasAttachmentLoader_create(atlas.c_ptr());
        if atlas_attachment_loader.is_null() {
            println!("failed to create attachment loader");
            return;
        } else {
            println!("Attachment loader is valid.");
        }

        let attachment_loader = &mut (*atlas_attachment_loader).super_0;

        let c_name = std::ffi::CString::new(name).unwrap();
        let c_path = std::ffi::CString::new(path).unwrap();
        let attachment = c::spAttachmentLoader_createAttachment(
            attachment_loader,
            std::ptr::null_mut(),
            c::SP_ATTACHMENT_REGION,
            c_name.as_ptr(),
            c_path.as_ptr(),
            std::ptr::null_mut(),
        );

        if attachment.is_null() {
            let error1: &std::ffi::CStr =
                std::ffi::CStr::from_ptr(attachment_loader.error1 as *const i8);
            let error2: &std::ffi::CStr =
                std::ffi::CStr::from_ptr(attachment_loader.error2 as *const i8);

            println!("Failed to create attachment. {:?} {:?}", error1, error2);
            return;
        } else {
            println!("Attachment is valid.");
        }

        let c_region = attachment as *mut c::spRegionAttachment;
        let region = &mut (*c_region);

        region.path = c_path.as_ptr();
        region.x = 0.0;
        region.y = 0.0;
        region.scaleX = 1.0;
        region.scaleY = 1.0;
        region.rotation = 0.0;
        region.width = 8.0;
        region.height = 8.0;
        region.color.r = 0.5;
        region.color.g = 0.5;
        region.color.b = 0.5;
        region.color.a = 0.5;
        region.sequence = std::ptr::null_mut();

        c::spRegionAttachment_updateRegion(region);
        c::spAttachmentLoader_configureAttachment(attachment_loader, attachment);

        Attachment::new_from_ptr(attachment)
    };
    unsafe {
        let Some(front_thigh_slot) = skeleton_controller.skeleton.find_slot_mut("front-thigh")
        else {
            println!("Failed to find slot.");
            return;
        };

        println!("TEST: {:?}", attachment.as_region());
        //front_thigh_slot.as_mut().set_attachment(Some(attachment));
        c::spSlot_setAttachment(front_thigh_slot.c_ptr(), attachment.c_ptr());
        println!("Custom attachment set!");
    }
  front-thigh
    4 Vertices / UVs
    6 Indices
    Normal Blend Mode
    Color { r: 0.5, g: 0.5, b: 0.5, a: 0.5 }
    No Premultiplied Alpha

Here front-thigh was rendered with our custom color that came from the injected attachment.

brandon-reinhart commented 11 months ago

head_on_head

Lol, got it working. Here is spineboy with a dynamic attachment of a small head occupying his front-shin slot.

I will need help with the pointer management -- this crashes when things are freed.

Not sure what you'll want the API to look like. My brief experiment so far is:

        let attachment_loader = AttachmentLoader::new_atlas_loader(&atlas);
        let attachment = attachment_loader
            .create_attachment(None, AttachmentType::Region, "test", "head")
            .unwrap();

Things get real sad if you drop attachment after setting it on a slot.

jabuwu commented 10 months ago

We merged #20 which adds support for region attachments, but I'm going to leave this PR open, since I'd like to look into creating the other attachments types as well.