#mesh #tf #rule #composable #replicate #3d #structures

immense

A library for building 3D structures with simple composable rules

5 releases

0.1.4 Dec 1, 2018
0.1.3 Nov 24, 2018
0.1.2 Nov 20, 2018
0.1.1 Nov 17, 2018
0.1.0 Nov 17, 2018

#117 in Data formats

27 downloads per month

Apache-2.0

46KB
804 lines

immense

crates.io

A library for describing 3D meshes with simple composable rules.

rule![
    tf![
        Tf::saturation(0.8),
        Tf::hue(160.0),
        Replicate::n(36, vec![Tf::rz(10.0), Tf::ty(0.1)]),
        Replicate::n(36, vec![Tf::ry(10.0), Tf::tz(1.2), Tf::hue(3.4)]),
    ] => cube()
]


lib.rs:

immense describes 3D structures with simple composable rules and outputs them as Wavefront object files you can plug into your renderer of choice.

Demo

 # use immense::*;
 Rule::new().push(vec![
     Replicate::n(1, vec![Tf::saturation(0.8), Tf::hue(160.0)]),
     Replicate::n(36, vec![Tf::rz(10.0), Tf::ty(0.1)]),
     Replicate::n(36, vec![Tf::ry(10.0), Tf::tz(1.2), Tf::hue(3.4)]),
    ],
    cube(),
)
 # ;

Table of Contents

  1. Intro
  2. Composing Rules
    1. Recursion
    2. Randomness
  3. Color
  4. Ergonomics Macros
  5. Custom Meshes

Intro

In immense, you create a Rule that describes your structure, which is ultimately composed of meshes. immense provides some builtin meshes, such as cube, and you can create your own rules by using these builtins which you'll see in the next section.

After you've built your Rule, you can export the meshes it expands to as a Wavefront object file for the next part of your workflow, whether that is rendering it in Blender, printing it in your 3D printer, or importing it into your game!

Composing Rules

Let's start with a cube. You probably want to write your meshes to a file and watch them in a viewer with autoreload. Meshlab is a great viewer (and much more) that can reload your meshes when changed. Check out ExportConfig to see what options you can set that will work best for your rendering or printing workflow.

use immense::*;
use std::fs::File;

let rule = cube();
let meshes = rule.generate();
let mut output_file = File::create("my_mesh.obj")?;
write_meshes(ExportConfig::default(), meshes, &mut output_file)?;

We can translate the cube with the Tf::t* family of functions which generate translate transforms. We'll apply Tf::tx by creating our own rule and invoking the cube rule with a transform.

let rule = Rule::new().push(Tf::tx(3.0), cube());

We can replicate transforms with Replicate which generates multiple invocations of a subrule, each with more applications of the same transform applied to it.

let rule = Rule::new().push(Replicate::n(3, Tf::ty(1.1)), cube());

Notice that our translation is 1.1 and that that is 0.1 more than the length of our cube. That's no coincidence! All the built in meshes are 1 in length so that you can use convenient measurements like this, even when deep in a transform stack.

Recursion

You can generate rules recursively with the api we've covered so far, but doing so would put your entire rule tree in memory at one time, which can become a problem. immense provides a trait, ToRule, so you can give it types that can instantiate rules when needed.

struct RecursiveTile {
    depth_budget: usize,
}

impl ToRule for RecursiveTile {
    fn to_rule(&self) -> Rule {
        let rule = Rule::new()
            .push(vec![Tf::t(0.25, 0.25, 0.0), Tf::s(0.4)], cube())
            .push(vec![Tf::t(-0.25, -0.25, 0.0), Tf::s(0.4)], cube())
            .push(vec![Tf::t(-0.25, 0.25, 0.0), Tf::s(0.4)], cube());
        if self.depth_budget > 0 {
            rule.push(
                vec![Tf::t(0.25, -0.25, 0.0), Tf::s(0.4)],
                RecursiveTile {
                    depth_budget: self.depth_budget - 1,
                },
            )
        } else {
            rule
        }
    }
}

let rule = RecursiveTile {
    depth_budget: 3
}.to_rule();

Randomness

Using ToRule to delay rule construction, we can sample some random values each time our type builds a rule.

struct RandCube;

impl ToRule for RandCube {
    fn to_rule(&self) -> Rule {
        Rule::new().push(
            *thread_rng()
                .choose(&[Tf::tx(0.1),
                          Tf::tx(-0.1),
                          Tf::tx(0.2),
                          Tf::tx(-0.2)])
                .unwrap(),
            cube(),
        )
    }
}

let rule = Rule::new().push(Replicate::n(4, Tf::ty(1.0)),
                            RandCube {});

Color

immense can export some colors alongside your mesh, by linking the object file output to an mtl file (material library). Set the output mtl file in export_colors and immense will write out colors.

You can specify colors overrides and transforms in HSV color space using Ogeon's [palette][palette]. See Tf::color, Tf::hue, Tf::saturation, Tf::value.

Ergonomics Macros

immense provides two ergonomics macros that make defining rules and transform sequences a little easier once you have an intuition for their semantics. They are rule! and [tf!], which help compose rules and transform sequences respectively.

They transform the demo code above into:

rule![
    tf![
        Tf::saturation(0.8),
        Tf::hue(160.0),
        Replicate::n(36, vec![Tf::rz(10.0), Tf::ty(0.1)]),
        Replicate::n(36, vec![Tf::ry(10.0), Tf::tz(1.2), Tf::hue(3.4)]),
    ] => cube(),
]

Custom Meshes

You can create meshes on your own and use them as rules by calling Mesh::from if you format your meshes according to object file format.

Meshes can be expensive to allocate. immense handles the primitives on your behalf, but if you introduce your own meshes you must be careful not to allocate them more than once. One million references to a sphere are fine, one million spheres will probably kill the process.

An example is the sphere builtin which allows you to create a potentially expensive sphere estimation:

let sphere: Rc<Mesh> = sphere(/*resolution=*/4);
let rule = Rule::new().push(Tf::s(2.0), sphere);

Dependencies

~7MB
~142K SLoC