diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000000000000000000000000000000000..6ef8d1a11ed0a8bcbd16f7d98789ae32f9fe504a
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,45 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug executable 'erroccfisumreg'",
+            "cargo": {
+                "args": [
+                    "build",
+                    "--bin=erroccfisumreg",
+                    "--package=erroccfisumreg"
+                ],
+                "filter": {
+                    "name": "erroccfisumreg",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug unit tests in executable 'erroccfisumreg'",
+            "cargo": {
+                "args": [
+                    "test",
+                    "--no-run",
+                    "--bin=erroccfisumreg",
+                    "--package=erroccfisumreg"
+                ],
+                "filter": {
+                    "name": "erroccfisumreg",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index cfb15f92b9e88aa11ba0baca2bfcab3cf7b0b4ea..abb1f9b6a283c527d6099518193bedebc0a718e1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -721,6 +721,9 @@ dependencies = [
  "egui_winit_vulkano",
  "obj",
  "rodio",
+ "serde",
+ "serde_json",
+ "utf-8",
  "vulkano",
  "vulkano-shaders",
  "vulkano-util",
@@ -2212,6 +2215,12 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
 [[package]]
 name = "vec_map"
 version = "0.8.2"
diff --git a/Cargo.toml b/Cargo.toml
index 20f20fe1cabcd97bd10e47b5b1dfb30c80688fab..06d14320bbe8ad9b3d6ed26242d53ff3d2a9ae85 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,6 +33,10 @@ egui_winit_vulkano = "0.22"
 
 ash = "*"
 
+serde = { version = "*", features = ["derive"] }
+serde_json = "*"
+
+utf-8 = "*"
 
 # using latest gits
 [patch.crates-io]
diff --git a/src/gui.rs b/src/gui.rs
index c6556e3e4b07facbdd1d5ff28c2bf395f915e0dc..7649105489edeca0e1052da65699564e46a6ac86 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -4,7 +4,7 @@ use egui::{
 };
 use egui_winit_vulkano::Gui;
 
-use crate::objects::{Light, Mesh};
+use crate::objects::{Light, Mesh, CSG};
 
 fn sized_text(ui: &mut egui::Ui, text: impl Into<String>, size: f32) {
     ui.label(egui::RichText::new(text).size(size));
@@ -17,6 +17,7 @@ pub struct GState {
 
     pub meshes: Vec<Mesh>,
     pub lights: Vec<Light>,
+    pub csg: Vec<CSG>,
 
     pub fps: [f64; 128],
 }
@@ -29,6 +30,7 @@ impl Default for GState {
 
             meshes: vec![],
             lights: vec![],
+            csg: vec![],
 
             fps: [0.0; 128],
         }
diff --git a/src/main.rs b/src/main.rs
index cb480fa4da95cb79f58ffac0da86f667fca77add..9ebea850ad6b694eaa2d431f0105b3e571df2cd5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+#![feature(variant_count)]
 use cgmath::{Deg, EuclideanSpace, Euler, Matrix3, Matrix4, Point3, Rad, SquareMatrix, Vector3};
 use std::io::Cursor;
 use std::{sync::Arc, time::Instant};
@@ -18,7 +19,7 @@ use vulkano::pipeline::graphics::vertex_input::Vertex;
 use vulkano::pipeline::PipelineBindPoint;
 use vulkano::shader::{ShaderModule, SpecializationConstants};
 use vulkano::swapchain::{PresentMode, SwapchainPresentInfo};
-use vulkano::{Version, VulkanLibrary};
+use vulkano::VulkanLibrary;
 use winit::event::{DeviceEvent, ElementState, MouseButton, VirtualKeyCode};
 
 use egui_winit_vulkano::Gui;
@@ -58,6 +59,7 @@ mod gui;
 use crate::gui::*;
 mod objects;
 use crate::objects::*;
+mod mcsg_deserialise;
 
 pub type MemoryAllocator = StandardMemoryAllocator;
 
@@ -370,11 +372,23 @@ fn main() {
         d: false,
     };
 
-    gstate.meshes.push(load_obj(
-        &memory_allocator,
-        &mut Cursor::new(PLATONIC_SOLIDS[0].1),
-        PLATONIC_SOLIDS[0].0.to_string(),
-    ));
+    gstate.meshes.push(
+        load_obj(
+            &memory_allocator,
+            &mut Cursor::new(PLATONIC_SOLIDS[0].1),
+            PLATONIC_SOLIDS[0].0.to_string(),
+        )
+        .unwrap(),
+    );
+
+    gstate.csg.push(
+        load_csg(
+            &memory_allocator,
+            &mut Cursor::new(CSG_SOLIDS[0].1),
+            CSG_SOLIDS[0].0.to_string(),
+        )
+        .unwrap(),
+    );
 
     gstate
         .lights
diff --git a/src/mcsg_deserialise.rs b/src/mcsg_deserialise.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b81f5bdb60e0ae8049fab811c7ced612923bb643
--- /dev/null
+++ b/src/mcsg_deserialise.rs
@@ -0,0 +1,516 @@
+use std::fmt::Display;
+use std::io::{BufReader, Cursor, Read};
+use std::ops::{AddAssign, MulAssign, Neg};
+use std::str::Chars;
+
+use serde::de::{
+    self, DeserializeSeed, EnumAccess, IntoDeserializer, MapAccess, SeqAccess, VariantAccess,
+    Visitor,
+};
+use serde::{forward_to_deserialize_any, Deserialize};
+use utf8::{BufReadDecoder, BufReadDecoderError};
+
+type Result<T> = core::result::Result<T, Error>;
+
+#[derive(Debug)]
+pub enum Error {
+    Message(String),
+    UTF8(String),
+    TrailingCharacters,
+    Eof,
+    ExpectedString,
+    Syntax,
+    ExpectedArrayEnd,
+    ExpectedArray,
+    ExpectedMapEnd,
+    ExpectedMap,
+    ExpectedMapColon,
+    ExpectedEnum,
+}
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Error::Message(msg) | Error::UTF8(msg) => f.write_str(msg),
+            _ => f.write_fmt(format_args!("Error: {:?}", self)),
+        }
+    }
+}
+impl std::error::Error for Error {}
+impl serde::de::Error for Error {
+    fn custom<T: Display>(msg: T) -> Self {
+        Error::Message(msg.to_string())
+    }
+}
+impl serde::ser::Error for Error {
+    fn custom<T: Display>(msg: T) -> Self {
+        Error::Message(msg.to_string())
+    }
+}
+
+pub struct Deserializer<'de> {
+    // This string starts with the input data and characters are truncated off
+    // the beginning as data is parsed.
+    input: BufReadDecoder<BufReader<&'de mut dyn Read>>,
+    read_buf: String,
+    peek: Option<(char, usize)>,
+}
+
+impl<'de> Deserializer<'de> {
+    // By convention, `Deserializer` constructors are named like `from_xyz`.
+    // That way basic use cases are satisfied by something like
+    // `serde_json::from_str(...)` while advanced use cases that require a
+    // deserializer can make one with `serde_json::Deserializer::from_str(...)`.
+
+    pub fn from_reader(input: &'de mut dyn Read) -> Self {
+        Deserializer {
+            input: BufReadDecoder::new(BufReader::new(input)),
+            read_buf: String::new(),
+            peek: None,
+        }
+    }
+}
+
+// By convention, the public API of a Serde deserializer is one or more
+// `from_xyz` methods such as `from_str`, `from_bytes`, or `from_reader`
+// depending on what Rust types the deserializer is able to consume as input.
+//
+// This basic deserializer supports only `from_str`.
+pub fn from_reader<'a, T>(s: &'a mut dyn Read) -> Result<T>
+where
+    T: Deserialize<'a>,
+{
+    let mut deserializer = Deserializer::from_reader(s);
+    let t = T::deserialize(&mut deserializer)?;
+    if deserializer.check_end() {
+        Ok(t)
+    } else {
+        Err(Error::TrailingCharacters)
+    }
+}
+
+fn get_char(mut s: Chars) -> Result<(char, usize)> {
+    let mut count = 0usize;
+    loop {
+        let next = s.next().ok_or(Error::Eof);
+        count += 1;
+        if next.as_ref().map_or(false, |&c| c.is_whitespace()) {
+            continue;
+        }
+        if next.as_ref().map_or(false, |this| this == &'/') {
+            if s.next().map_or(false, |this| this == '/') {
+                while s.next().map_or(false, |s| s != '\n' && s != '\r') {
+                    count += 1;
+                }
+                count += 1;
+            }
+            count += 1;
+        } else {
+            return next.map(|c| (c, count));
+        }
+    }
+}
+
+// SERDE IS NOT A PARSING LIBRARY. This impl block defines a few basic parsing
+// functions from scratch. More complicated formats may wish to use a dedicated
+// parsing library to help implement their Serde deserializer.
+impl<'de> Deserializer<'de> {
+    fn check_end(&mut self) -> bool {
+        self.input.next_strict().is_none()
+    }
+
+    // Look at the first character in the input without consuming it.
+    fn peek_char(&mut self) -> Result<(char, usize)> {
+        if let Some(p) = self.peek {
+            Ok(p)
+        } else {
+            loop {
+                match get_char(self.read_buf.chars()) {
+                    Ok(c) => {
+                        self.peek = Some(c);
+                        return Ok(c);
+                    }
+                    Err(Error::Eof) => match self.input.next_strict() {
+                        Some(e) => {
+                            match e {
+                                Ok(s) => {
+                                    s.clone_into(&mut self.read_buf);
+                                }
+                                Err(e) => return Err(Error::UTF8(format!("{}", e))),
+                            };
+                        }
+                        None => return Err(Error::Eof),
+                    },
+                    Err(e) => return Err(e),
+                }
+            }
+        }
+    }
+
+    // Consume the first character in the input.
+    fn next_char(&mut self) -> Result<char> {
+        let ch = self.peek_char()?;
+        self.read_buf = self.read_buf[ch.1..].to_string();
+        self.peek = None;
+        Ok(ch.0)
+    }
+
+    // Parse a string until the next '"' character.
+    //
+    // Makes no attempt to handle escape sequences. What did you expect? This is
+    // example code!
+    fn parse_string(&mut self) -> Result<String> {
+        if self.next_char()? != '"' {
+            return Err(Error::ExpectedString);
+        }
+        loop {
+            match self.read_buf.find('"') {
+                Some(len) => {
+                    let out = self.read_buf[..len].to_string();
+                    self.read_buf = self.read_buf[len + 1..].to_string();
+                    return Ok(out);
+                }
+                None => match self.input.next_strict() {
+                    Some(e) => {
+                        match e {
+                            Ok(s) => {
+                                s.clone_into(&mut self.read_buf);
+                            }
+                            Err(e) => return Err(Error::UTF8(format!("{}", e))),
+                        };
+                    }
+                    None => return Err(Error::Eof),
+                },
+            }
+        }
+    }
+}
+
+impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
+    type Error = Error;
+
+    // Look at the input data to decide what Serde data model type to
+    // deserialize as. Not all data formats are able to support this operation.
+    // Formats that support `deserialize_any` are known as self-describing.
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        match self.peek_char()?.0 {
+            '"' => self.deserialize_str(visitor),
+            '[' => self.deserialize_seq(visitor),
+            '{' => self.deserialize_map(visitor),
+            _ => Err(Error::Syntax),
+        }
+    }
+
+    // Refer to the "Understanding deserializer lifetimes" page for information
+    // about the three deserialization flavors of strings in Serde.
+    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_string(self.parse_string()?)
+    }
+
+    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_string(visitor)
+    }
+
+    // Deserialization of compound types like sequences and maps happens by
+    // passing the visitor an "Access" object that gives it the ability to
+    // iterate through the data contained in the sequence.
+    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        // Parse the opening bracket of the sequence.
+        if self.next_char()? == '[' {
+            // Give the visitor access to each element of the sequence.
+            let value = visitor.visit_seq(CommaSeparated::new(self))?;
+            // Parse the closing bracket of the sequence.
+            if self.next_char()? == ']' {
+                Ok(value)
+            } else {
+                Err(Error::ExpectedArrayEnd)
+            }
+        } else {
+            Err(Error::ExpectedArray)
+        }
+    }
+
+    // Much like `deserialize_seq` but calls the visitors `visit_map` method
+    // with a `MapAccess` implementation, rather than the visitor's `visit_seq`
+    // method with a `SeqAccess` implementation.
+    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        // Parse the opening brace of the map.
+        if self.next_char()? == '{' {
+            // Give the visitor access to each entry of the map.
+            let value = visitor.visit_map(CommaSeparated::new(self))?;
+            // Parse the closing brace of the map.
+            if self.next_char()? == '}' {
+                Ok(value)
+            } else {
+                Err(Error::ExpectedMapEnd)
+            }
+        } else {
+            Err(Error::ExpectedMap)
+        }
+    }
+
+    // Structs look just like maps in JSON.
+    //
+    // Notice the `fields` parameter - a "struct" in the Serde data model means
+    // that the `Deserialize` implementation is required to know what the fields
+    // are before even looking at the input data. Any key-value pairing in which
+    // the fields cannot be known ahead of time is probably a map.
+    fn deserialize_struct<V>(
+        self,
+        _name: &'static str,
+        _fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_map(visitor)
+    }
+
+    // An identifier in Serde is the type that identifies a field of a struct or
+    // the variant of an enum. In JSON, struct fields and enum variants are
+    // represented as strings. In other formats they may be represented as
+    // numeric indices.
+    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_str(visitor)
+    }
+
+    // Tuples look just like sequences in JSON. Some formats may be able to
+    // represent tuples more efficiently.
+    //
+    // As indicated by the length parameter, the `Deserialize` implementation
+    // for a tuple in the Serde data model is required to know the length of the
+    // tuple before even looking at the input data.
+    fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_seq(visitor)
+    }
+
+    // Tuple structs look just like sequences in JSON.
+    fn deserialize_tuple_struct<V>(
+        self,
+        _name: &'static str,
+        _len: usize,
+        visitor: V,
+    ) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_seq(visitor)
+    }
+
+    fn deserialize_enum<V>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        if self.peek_char()?.0 == '"' {
+            // Visit a unit variant.
+            visitor.visit_enum(self.parse_string()?.into_deserializer())
+        } else if self.next_char()? == '{' {
+            // Visit a newtype variant, tuple variant, or struct variant.
+            let value = visitor.visit_enum(Enum::new(self))?;
+            // Parse the matching close brace.
+            if self.next_char()? == '}' {
+                Ok(value)
+            } else {
+                Err(Error::ExpectedMapEnd)
+            }
+        } else {
+            Err(Error::ExpectedEnum)
+        }
+    }
+
+    // As is done here, serializers are encouraged to treat newtype structs as
+    // insignificant wrappers around the data they contain. That means not
+    // parsing anything other than the contained value.
+    fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
+    // Like `deserialize_any` but indicates to the `Deserializer` that it makes
+    // no difference which `Visitor` method is called because the data is
+    // ignored.
+    //
+    // Some deserializers are able to implement this more efficiently than
+    // `deserialize_any`, for example by rapidly skipping over matched
+    // delimiters without paying close attention to the data in between.
+    //
+    // Some formats are not able to implement this at all. Formats that can
+    // implement `deserialize_any` and `deserialize_ignored_any` are known as
+    // self-describing.
+    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_any(visitor)
+    }
+
+    forward_to_deserialize_any! {
+        bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char bytes byte_buf option unit unit_struct
+    }
+}
+
+// In order to handle commas correctly when deserializing a JSON array or map,
+// we need to track whether we are on the first element or past the first
+// element.
+struct CommaSeparated<'a, 'de: 'a> {
+    de: &'a mut Deserializer<'de>,
+}
+
+impl<'a, 'de> CommaSeparated<'a, 'de> {
+    fn new(de: &'a mut Deserializer<'de>) -> Self {
+        CommaSeparated { de }
+    }
+}
+
+// `SeqAccess` is provided to the `Visitor` to give it the ability to iterate
+// through elements of the sequence.
+impl<'de, 'a> SeqAccess<'de> for CommaSeparated<'a, 'de> {
+    type Error = Error;
+
+    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
+    where
+        T: DeserializeSeed<'de>,
+    {
+        // Check if there are no more elements.
+        if self.de.peek_char()?.0 == ']' {
+            return Ok(None);
+        }
+        // Deserialize an array element.
+        seed.deserialize(&mut *self.de).map(Some)
+    }
+}
+
+// `MapAccess` is provided to the `Visitor` to give it the ability to iterate
+// through entries of the map.
+impl<'de, 'a> MapAccess<'de> for CommaSeparated<'a, 'de> {
+    type Error = Error;
+
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
+    where
+        K: DeserializeSeed<'de>,
+    {
+        // Check if there are no more entries.
+        if self.de.peek_char()?.0 == '}' {
+            return Ok(None);
+        }
+        // Deserialize a map key.
+        seed.deserialize(&mut *self.de).map(Some)
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
+    where
+        V: DeserializeSeed<'de>,
+    {
+        // It doesn't make a difference whether the colon is parsed at the end
+        // of `next_key_seed` or at the beginning of `next_value_seed`. In this
+        // case the code is a bit simpler having it here.
+        if self.de.next_char()? != ':' {
+            return Err(Error::ExpectedMapColon);
+        }
+        // Deserialize a map value.
+        seed.deserialize(&mut *self.de)
+    }
+}
+
+struct Enum<'a, 'de: 'a> {
+    de: &'a mut Deserializer<'de>,
+}
+
+impl<'a, 'de> Enum<'a, 'de> {
+    fn new(de: &'a mut Deserializer<'de>) -> Self {
+        Enum { de }
+    }
+}
+
+// `EnumAccess` is provided to the `Visitor` to give it the ability to determine
+// which variant of the enum is supposed to be deserialized.
+//
+// Note that all enum deserialization methods in Serde refer exclusively to the
+// "externally tagged" enum representation.
+impl<'de, 'a> EnumAccess<'de> for Enum<'a, 'de> {
+    type Error = Error;
+    type Variant = Self;
+
+    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
+    where
+        V: DeserializeSeed<'de>,
+    {
+        // The `deserialize_enum` method parsed a `{` character so we are
+        // currently inside of a map. The seed will be deserializing itself from
+        // the key of the map.
+        let val = seed.deserialize(&mut *self.de)?;
+        // Parse the colon separating map key from value.
+        if self.de.next_char()? == ':' {
+            Ok((val, self))
+        } else {
+            Err(Error::ExpectedMapColon)
+        }
+    }
+}
+
+// `VariantAccess` is provided to the `Visitor` to give it the ability to see
+// the content of the single variant that it decided to deserialize.
+impl<'de, 'a> VariantAccess<'de> for Enum<'a, 'de> {
+    type Error = Error;
+
+    // If the `Visitor` expected this variant to be a unit variant, the input
+    // should have been the plain string case handled in `deserialize_enum`.
+    fn unit_variant(self) -> Result<()> {
+        Err(Error::ExpectedString)
+    }
+
+    // Newtype variants are represented in JSON as `{ NAME: VALUE }` so
+    // deserialize the value here.
+    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
+    where
+        T: DeserializeSeed<'de>,
+    {
+        seed.deserialize(self.de)
+    }
+
+    // Tuple variants are represented in JSON as `{ NAME: [DATA...] }` so
+    // deserialize the sequence of data here.
+    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        de::Deserializer::deserialize_seq(self.de, visitor)
+    }
+
+    // Struct variants are represented in JSON as `{ NAME: { K: V, ... } }` so
+    // deserialize the inner map here.
+    fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        de::Deserializer::deserialize_map(self.de, visitor)
+    }
+}
diff --git a/src/objects.rs b/src/objects.rs
index d974887d6522ed390a75bbfece658a531925d157..55f866704ed3570ebfdca00dba32edc109591270 100644
--- a/src/objects.rs
+++ b/src/objects.rs
@@ -1,16 +1,27 @@
-use std::{collections::HashMap, io::Read};
+use std::{
+    collections::HashMap,
+    io::{Cursor, Read},
+    mem,
+    num::ParseFloatError,
+};
 
 use bytemuck::{Pod, Zeroable};
-use cgmath::{Deg, Euler, Point3, Vector3};
-use obj::{LoadConfig, ObjData};
+use cgmath::{Deg, EuclideanSpace, Euler, Matrix3, Point3, SquareMatrix, Vector3};
+use obj::{LoadConfig, ObjData, ObjError};
+use serde::{Deserialize, Serialize};
 use vulkano::{
     buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer},
+    half::f16,
     pipeline::graphics::vertex_input::Vertex,
 };
 
-use crate::MemoryAllocator;
+use crate::{
+    mcsg_deserialise::{from_reader, Deserializer},
+    MemoryAllocator,
+};
 
 pub const PLATONIC_SOLIDS: [(&str, &[u8]); 1] = [("Buny", include_bytes!("bunny.obj"))];
+pub const CSG_SOLIDS: [(&str, &[u8]); 1] = [("Primitives", include_bytes!("primitive.mcsg"))];
 
 // We now create a buffer that will store the shape of our triangle.
 // We use #[repr(C)] here to force rustc to not do anything funky with our data, although for this
@@ -31,11 +42,54 @@ pub struct Mesh {
     pub indices: Subbuffer<[u32]>,
     pub pos: Point3<f32>,
     pub rot: Euler<Deg<f32>>,
-    pub scale: f32,
+    pub scale: Vector3<f32>,
+}
+
+#[derive(Debug)]
+pub struct CSG {
+    pub name: String,
+    pub parts: Subbuffer<[CSGPart]>,
+    pub pos: Point3<f32>,
+    pub rot: Euler<Deg<f32>>,
+    pub scale: Vector3<f32>,
 }
 
-pub fn load_obj(memory_allocator: &MemoryAllocator, input: &mut dyn Read, name: String) -> Mesh {
-    let object = ObjData::load_buf_with_config(input, LoadConfig::default()).unwrap();
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
+pub struct CSGPart {
+    #[format(R16_SFLOAT)]
+    code: u16,
+}
+
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, Default)]
+pub enum CSGOpcode {
+    Infinity = 0, // 0 is actually FP16 infinity, perfectly normal representation
+    #[default]
+    None,
+    Const,
+}
+
+impl CSGPart {
+    pub fn opcode(opcode: CSGOpcode) -> CSGPart {
+        CSGPart {
+            code: opcode as u16 | 0b0111110000000000,
+        }
+    }
+
+    pub fn literal(literal: f16) -> CSGPart {
+        CSGPart {
+            code: literal.to_bits(),
+        }
+    }
+}
+
+pub fn load_obj(
+    memory_allocator: &MemoryAllocator,
+    input: &mut dyn Read,
+    name: String,
+) -> Result<Mesh, ObjError> {
+    let object = ObjData::load_buf_with_config(input, LoadConfig::default())?;
 
     let mut vertices = vec![];
 
@@ -87,7 +141,7 @@ pub fn load_obj(memory_allocator: &MemoryAllocator, input: &mut dyn Read, name:
     )
     .unwrap();
 
-    Mesh {
+    Ok(Mesh {
         vertices: vertex_buffer,
         indices: index_buffer,
         pos: Point3 {
@@ -96,9 +150,237 @@ pub fn load_obj(memory_allocator: &MemoryAllocator, input: &mut dyn Read, name:
             z: 0.,
         },
         rot: Euler::new(Deg(0.), Deg(0.), Deg(0.)),
-        scale: 1.,
+        scale: Vector3 {
+            x: 1.,
+            y: 1.,
+            z: 1.,
+        },
         name,
-    }
+    })
+}
+
+#[derive(Serialize, Deserialize)]
+struct MCSG {
+    object: Vec<MCSGObject>,
+    csg: Vec<MCSGCSG>,
+}
+type MCSGObject = HashMap<String, String>;
+type MCSGCSG = Vec<MCSGCSGPart>;
+type MCSGCSGPart = HashMap<String, String>;
+
+fn matrix3_from_string(input: &String) -> Result<Matrix3<f32>, String> {
+    let vec = input
+        .split(" ")
+        .map(|s| s.parse::<f32>())
+        .collect::<Result<Vec<f32>, _>>()
+        .map_err(|_| "not floats")?;
+    let array: [f32; 9] = vec.try_into().map_err(|_| "wrong number of values")?;
+    let matrix: &Matrix3<f32> = (&array).into();
+    Ok(*matrix)
+}
+
+fn vector3_from_string(input: &String) -> Result<Vector3<f32>, String> {
+    let vec = input
+        .split(" ")
+        .map(|s| s.parse::<f32>())
+        .collect::<Result<Vec<f32>, _>>()
+        .map_err(|_| "not floats")?;
+    let array: [f32; 3] = vec.try_into().map_err(|_| "wrong number of values")?;
+    let vector: &Vector3<f32> = (&array).into();
+    Ok(*vector)
+}
+
+fn point3_from_string(input: &String) -> Result<Point3<f32>, String> {
+    let vec = input
+        .split(" ")
+        .map(|s| s.parse::<f32>())
+        .collect::<Result<Vec<f32>, _>>()
+        .map_err(|_| "not floats")?;
+    let array: [f32; 3] = vec.try_into().map_err(|_| "wrong number of values")?;
+    let point: &Point3<f32> = (&array).into();
+    Ok(*point)
+}
+struct TRS {
+    translation: Point3<f32>,
+    rotation: Matrix3<f32>,
+    scale: Vector3<f32>,
+}
+
+fn get_trs(o: &HashMap<String, String>) -> Result<TRS, String> {
+    Ok(TRS {
+        translation: o
+            .get("t")
+            .map(point3_from_string)
+            .transpose()
+            .map_err(|e| e.to_string())?
+            .unwrap_or(Point3::origin()),
+        rotation: o
+            .get("r")
+            .map(matrix3_from_string)
+            .transpose()
+            .map_err(|e| e.to_string())?
+            .unwrap_or(Matrix3::identity()),
+        scale: o
+            .get("s")
+            .map(vector3_from_string)
+            .transpose()
+            .map_err(|e| e.to_string())?
+            .unwrap_or(Vector3 {
+                x: 1.,
+                y: 1.,
+                z: 1.,
+            }),
+    })
+}
+fn get_color(o: &HashMap<String, String>) -> Result<Vector3<f32>, String> {
+    Ok(o.get("color")
+        .map(vector3_from_string)
+        .transpose()
+        .map_err(|e| e.to_string())?
+        .unwrap_or(Vector3 {
+            x: 255.,
+            y: 255.,
+            z: 255.,
+        }))
+}
+
+fn get_rgb(o: &HashMap<String, String>) -> Result<Vector3<f32>, String> {
+    Ok(o.get("rgb")
+        .map(vector3_from_string)
+        .transpose()
+        .map_err(|e| e.to_string())?
+        .unwrap_or(Vector3 {
+            x: 255.,
+            y: 255.,
+            z: 255.,
+        }))
+}
+
+fn get_f32(o: &HashMap<String, String>, tag: &str) -> Result<f32, String> {
+    get_f32_default(o, tag, 0.)
+}
+
+fn get_f32_default(o: &HashMap<String, String>, tag: &str, default: f32) -> Result<f32, String> {
+    Ok(o.get(tag)
+        .map(|c| c.parse::<f32>())
+        .transpose()
+        .map_err(|e| e.to_string())?
+        .unwrap_or_default())
+}
+
+fn get_percentage(o: &HashMap<String, String>, tag: &str) -> Result<f32, String> {
+    get_f32(o, tag).map(|c| c / 100.0)
+}
+
+fn get_percentage_default(
+    o: &HashMap<String, String>,
+    tag: &str,
+    default: f32,
+) -> Result<f32, String> {
+    get_f32_default(o, tag, default).map(|c| c / 100.0)
+}
+
+#[repr(u8)]
+enum Half {
+    X,
+    Y,
+    Z,
+    XM,
+    YM,
+    ZM,
+}
+
+fn get_half(o: &HashMap<String, String>) -> Result<Half, String> {
+    Ok({
+        let half = o
+            .get("half")
+            .map(|c| c.parse::<u8>())
+            .transpose()
+            .map_err(|e| e.to_string())?
+            .unwrap_or(0);
+        if half as usize >= mem::variant_count::<Half>() {
+            return Err("invalid enum".to_owned());
+        }
+        unsafe { mem::transmute(half) }
+    })
+}
+
+pub fn load_csg(
+    memory_allocator: &MemoryAllocator,
+    input: &mut dyn Read,
+    name: String,
+) -> Result<Vec<CSG>, String> {
+    let mcsg: MCSG =
+        from_reader(&mut Cursor::new("{").chain(input).chain(Cursor::new("}"))).unwrap();
+
+    mcsg.object
+        .iter()
+        .enumerate()
+        .map(|(i, o)| {
+            let name = name + "_" + o.get("name").unwrap_or(&"unknown".to_owned());
+            let trs = get_trs(&o)?;
+            let color = get_color(&o)?;
+
+            let cid = o
+                .get("cid")
+                .map(|c| c.parse::<usize>())
+                .transpose()
+                .map_err(|e| e.to_string())?
+                .unwrap_or(0);
+            if o.get("type").map(|ty| &ty[..] != "csg").unwrap_or(false) {
+                return Err("Type unknown".to_owned());
+            }
+
+            let parts = mcsg
+                .csg
+                .get(cid)
+                .ok_or("unknown cid")?
+                .iter()
+                .map(|inpart| {
+                    let ty = inpart.get("type").ok_or("no type!")?.as_str();
+                    let mut csgpart = CSGPart::literal(0u8.into());
+                    match ty {
+                        "sphere" => {
+                            let blend = get_f32(&inpart, "blend")?;
+                            let shell = get_percentage(&inpart, "shell%")?;
+                            let power = get_f32_default(&inpart, "power", 2.)?;
+                            let rgb = get_rgb(&inpart)?;
+                            let trs = get_trs(&inpart)?;
+                            let half = get_half(&inpart)?;
+                        }
+                        _ => {} //return Err("unknown type of csg".to_owned()),
+                    }
+                    Ok(csgpart)
+                })
+                .collect::<Result<Vec<CSGPart>, String>>()?;
+
+            let parts_buffer = Buffer::from_iter(
+                memory_allocator,
+                BufferAllocateInfo {
+                    buffer_usage: BufferUsage::VERTEX_BUFFER,
+                    ..Default::default()
+                },
+                parts,
+            )
+            .unwrap();
+
+            Ok(CSG {
+                parts: parts_buffer,
+                pos: Point3 {
+                    x: 0.,
+                    y: 0.,
+                    z: 0.,
+                },
+                rot: Euler::new(Deg(0.), Deg(0.), Deg(0.)),
+                scale: Vector3 {
+                    x: 1.,
+                    y: 1.,
+                    z: 1.,
+                },
+                name,
+            })
+        })
+        .collect::<Result<Vec<CSG>, String>>()
 }
 
 #[derive(Debug)]
@@ -116,3 +398,16 @@ impl Light {
         }
     }
 }
+
+impl Default for Light {
+    fn default() -> Self {
+        Self {
+            pos: Point3::origin(),
+            colour: Vector3 {
+                x: 1.,
+                y: 1.,
+                z: 1.,
+            },
+        }
+    }
+}
diff --git a/src/primitive.mcsg b/src/primitive.mcsg
new file mode 100644
index 0000000000000000000000000000000000000000..f0f6b76fa47b043e911c62cfdeb832896bad013f
--- /dev/null
+++ b/src/primitive.mcsg
@@ -0,0 +1,941 @@
+"object" :
+[
+  {
+    "name": "objects"
+    "cid": "0"
+    "type": "csg"
+    "res": "64"
+    "color": "186 122 111"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "0 0 0"
+  }
+]
+"bg" :
+[
+]
+"csg" :
+[
+  //0
+  [
+  {
+    "type": "polygon"
+    "op": "torus"
+    "rgb": "247 126 92"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "n": "5"
+    "star%": "0.28"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-241.988 14.4664 71.172"
+    "s": "38.8816 38.8816 3.9999"
+  }
+  {
+    "type": "polygon"
+    "rgb": "247 126 92"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "0.17"
+    "n": "3"
+    "star%": "0.3"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-241.988 -169.015 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "polygon"
+    "rgb": "247 126 92"
+    "blend": "0.72"
+    "hole%": "0.55"
+    "bevel%": "0.21"
+    "n": "5"
+    "star%": "0.68"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-241.988 -79.5119 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "polygon"
+    "rgb": "247 126 92"
+    "blend": "0.72"
+    "cone%": "1"
+    "round%": "0.13"
+    "n": "5"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-241.988 104.438 69.3783"
+    "s": "34.0362 34.0362 23.3124"
+  }
+  {
+    "type": "polygon"
+    "rgb": "247 126 92"
+    "half": "4"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "hole%": "0.21"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "n": "5"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-241.988 191.374 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "triangle"
+    "op": "torus"
+    "rgb": "180 129 187"
+    "blend": "0.72"
+    "top_v%": "1"
+    "top_w%": "0.43"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-153.684 14.4664 71.172"
+    "s": "38.8816 38.8816 3.9999"
+  }
+  {
+    "type": "triangle"
+    "rgb": "180 129 187"
+    "blend": "0.72"
+    "top_v%": "0.31"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-153.684 -169.015 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "triangle"
+    "rgb": "180 129 187"
+    "blend": "0.72"
+    "hole%": "0.55"
+    "top_v%": "0.31"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-153.684 -79.5119 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "triangle"
+    "rgb": "180 129 187"
+    "blend": "0.72"
+    "cone%": "1"
+    "top_v%": "-1"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-153.684 104.438 101.94"
+    "s": "34.0362 34.0362 44.6027"
+  }
+  {
+    "type": "triangle"
+    "rgb": "180 129 187"
+    "half": "4"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "hole%": "0.21"
+    "top_v%": "0.31"
+    "top_w%": "0.61"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-153.684 191.374 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "cube"
+    "op": "torus"
+    "rgb": "88 178 220"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-59.9187 14.4665 71.1721"
+    "s": "38.8816 38.8816 3.9999"
+  }
+  {
+    "type": "cube"
+    "rgb": "88 178 220"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "1"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-59.9187 -169.015 69.3783"
+    "s": "40.1034 41.6193 16.4927"
+  }
+  {
+    "type": "polygon"
+    "rgb": "88 178 220"
+    "blend": "0.72"
+    "hole%": "0.55"
+    "bevel%": "0.21"
+    "n": "5"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-59.9187 -79.5119 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "cube"
+    "rgb": "88 178 220"
+    "blend": "0.72"
+    "cone%": "0.7"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-59.9187 104.438 103.193"
+    "s": "34.0362 34.0362 47.1071"
+  }
+  {
+    "type": "cube"
+    "rgb": "88 178 220"
+    "half": "4"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "hole%": "0.21"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-59.9187 191.374 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "bezier"
+    "op": "torus"
+    "rgb": "134 193 102"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "top_v%": "0.31"
+    "line_w": "11"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "49.4242 14.4665 71.1721"
+    "s": "38.8816 38.8816 3.9999"
+  }
+  {
+    "type": "bezier"
+    "rgb": "134 193 102"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "0.17"
+    "line_w": "11"
+    "line_t": "0.77"
+    "cpn"   : "4"
+    "v1": "36.7109 -82.2298 0"
+    "v2": "69.4205 -40.6086 0"
+    "v3": "-1.82643 -73.0623 0"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "29.5939 -167.167 69.3783"
+    "s": "35.6235 41.1149 16.4927"
+  }
+  {
+    "type": "bezier"
+    "rgb": "134 193 102"
+    "blend": "0.72"
+    "hole%": "0.55"
+    "bevel%": "0.21"
+    "top_v%": "0.31"
+    "line_w": "11"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "49.4242 -79.5119 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "bezier"
+    "rgb": "134 193 102"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "cone%": "1"
+    "round%": "0.13"
+    "top_v%": "0.31"
+    "line_w": "11"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "49.4242 104.438 69.3783"
+    "s": "34.0362 34.0362 23.3124"
+  }
+  {
+    "type": "bezier"
+    "rgb": "134 193 102"
+    "half": "4"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "hole%": "0.21"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "line_w": "25"
+    "cpn"   : "3"
+    "v1": "71.036 85.1658 0"
+    "v2": "70.7743 8.40234 0"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "17.6168 171.396 69.3783"
+    "s": "35.518 42.5829 16.4927"
+  }
+  {
+    "type": "cylinder"
+    "op": "torus"
+    "rgb": "112 124 116"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "164.076 14.4665 71.1721"
+    "s": "38.8816 38.8816 3.9999"
+  }
+  {
+    "type": "cylinder"
+    "rgb": "112 124 116"
+    "blend": "0.72"
+    "bevel%": "0.06"
+    "round%": "1"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "164.076 -169.015 69.3783"
+    "s": "34.0362 34.0362 101.907"
+  }
+  {
+    "type": "cylinder"
+    "rgb": "112 124 116"
+    "blend": "0.72"
+    "hole%": "0.55"
+    "bevel%": "0.21"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "164.076 -79.5118 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "cylinder"
+    "rgb": "112 124 116"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "cone%": "1"
+    "round%": "0.13"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "164.076 104.438 107.804"
+    "s": "34.0362 34.0362 50.9307"
+  }
+  {
+    "type": "cylinder"
+    "rgb": "112 124 116"
+    "half": "4"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "hole%": "0.21"
+    "bevel%": "0.06"
+    "round%": "0.69"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "164.076 191.374 69.3783"
+    "s": "34.0362 34.0362 16.4927"
+  }
+  {
+    "type": "sphere"
+    "rgb": "249 191 69"
+    "blend": "0.72"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "277.566 -70.0318 94.032"
+    "s": "38.8816 37.8267 81.005"
+  }
+  {
+    "type": "sphere"
+    "rgb": "249 191 69"
+    "half": "4"
+    "blend": "0.72"
+    "shell%": "0.67"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "277.566 -177.452 71.8416"
+    "s": "53.888 53.888 53.888"
+  }
+  {
+    "type": "jt"
+    "rgb": "225 166 121"
+    "blend": "0.72"
+    "line_w": "16"
+    "line_t": "0.42"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "283.517 107.874 88.6313"
+    "s": "16 33.3182 16"
+  }
+  {
+    "name": "polygon"
+    "type": "polygon"
+    "rgb": "247 126 92"
+    "blend": "0.72"
+    "bevel%": "0.05"
+    "round%": "0.11"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-241.988 -259.969 69.3783"
+    "s": "34.0362 34.0362 31.8128"
+  }
+  {
+    "name": "triangle"
+    "type": "triangle"
+    "rgb": "180 129 187"
+    "blend": "0.72"
+    "top_v%": "-1"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-147.505 -263.066 69.3783"
+    "s": "32.5919 33.7365 14.9794"
+  }
+  {
+    "type": "bezier"
+    "rgb": "134 193 102"
+    "blend": "0.72"
+    "line_w": "11"
+    "line_t": "0.86"
+    "cpn"   : "4"
+    "cp3d"   : "1"
+    "v1": "90.7305 -51.0184 -46.0015"
+    "v2": "56.2318 -0.434326 -31.4712"
+    "v3": "36.103 -119.806 -16.6211"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "15.9159 -293.365 66.177"
+    "s": "45.3652 59.903 34.0007"
+  }
+  {
+    "type": "cylinder"
+    "rgb": "93 172 129"
+    "blend": "0.72"
+    "cone%": "0.68"
+    "bevel%": "0.06"
+    "round%": "0.06"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "169.337 -245.935 52.9717"
+    "s": "48.8821 27.5035 60.9762"
+  }
+  {
+    "type": "cube"
+    "rgb": "88 178 220"
+    "blend": "0.72"
+    "bevel%": "1"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-76.329 -271.519 69.3783"
+    "s": "16.7095 42.7063 4.59684"
+  }
+  {
+    "type": "cylinder"
+    "rgb": "93 172 129"
+    "blend": "0.72"
+    "hole%": "0.53"
+    "bevel%": "0.06"
+    "round%": "0.34"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "174.848 -318.212 52.9717"
+    "s": "48.8821 27.5035 29.1618"
+  }
+  {
+    "type": "cylinder"
+    "rgb": "93 172 129"
+    "blend": "0.72"
+    "hole%": "0.53"
+    "bevel%": "0.06"
+    "round%": "0.34"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "288.785 -320.758 52.9717"
+    "s": "48.8821 27.5035 6.8466"
+  }
+  {
+    "type": "cube"
+    "op": "torus"
+    "rgb": "88 178 220"
+    "blend": "0.72"
+    "bevel%": "1"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-33.9907 -271.519 69.3783"
+    "s": "19.6374 42.7063 4.7077"
+  }
+  {
+    "type": "sphere"
+    "rgb": "249 191 69"
+    "blend": "0.72"
+    "power": "2.90"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "277.566 16.3762 94.032"
+    "s": "38.8816 37.8267 81.005"
+  }
+  {
+    "type": "jt"
+    "op": "revolve"
+    "rgb": "225 166 121"
+    "blend": "0.72"
+    "line_w": "31"
+    "line_t": "0.89"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "278.166 205.342 88.6313"
+    "s": "31 33.3182 31"
+  }
+  {
+    "type": "jt"
+    "rgb": "225 166 121"
+    "blend": "0.72"
+    "hole%": "0.61"
+    "line_w": "16"
+    "line_t": "0.42"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "403.632 114.486 88.6313"
+    "s": "16 33.3182 16"
+  }
+  {
+    "type": "jt"
+    "rgb": "225 166 121"
+    "blend": "0.72"
+    "cone%": "0.56"
+    "line_w": "16"
+    "line_t": "0.42"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "396.285 208.698 88.6313"
+    "s": "16 33.3182 16"
+  }
+  {
+    "type": "triangle"
+    "rgb": "219 121 142"
+    "lmy": "1"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-325.353 184.805 58.3003"
+    "s": "23.7624 35.625 13.72"
+  }
+  {
+    "type": "triangle"
+    "rgb": "219 121 142"
+    "hole%": "0.55"
+    "lmy": "1"
+    "bevel": "4.28"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-325.353 92.8812 58.3003"
+    "s": "23.7624 35.625 13.72"
+  }
+  {
+    "type": "triangle"
+    "rgb": "219 121 142"
+    "cone%": "0.71"
+    "hole%": "0.55"
+    "lmy": "1"
+    "bevel": "4.28"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-325.353 7.93176 58.3003"
+    "s": "23.7624 35.625 13.72"
+  }
+  {
+    "type": "polygon"
+    "op": "lmz"
+    "rgb": "225 121 121"
+    "blend": "0.54"
+    "cone%": "1"
+    "hole%": "0.61"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "394.82 -57.0846 84.193"
+    "s": "40.4477 40.4477 55.7464"
+  }
+  {
+    "type": "cylinder"
+    "op": "lmz"
+    "rgb": "225 121 121"
+    "blend": "0.52"
+    "cone%": "1"
+    "hole%": "0.61"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "394.006 -159.784 84.193"
+    "s": "38.7176 38.7176 53.3619"
+  }
+  {
+    "type": "bezier"
+    "op": "lmz"
+    "rgb": "225 121 121"
+    "blend": "0.44"
+    "cone%": "1"
+    "hole%": "0.61"
+    "line_w": "19"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "396.131 -259.546 84.193"
+    "s": "32.5422 32.5422 15.715"
+  }
+  {
+    "type": "triangle"
+    "op": "lmz"
+    "rgb": "225 121 121"
+    "blend": "0.54"
+    "cone%": "1"
+    "hole%": "0.61"
+    "lmy": "1"
+    "r": "-0.0348995 -0.999391 0 0.999391 -0.0348995 0 0 0 1"
+    "t": "383.098 28.0329 84.193"
+    "s": "40.4477 55.6998 77.4739"
+  }
+  {
+    "type": "jt"
+    "op": "revolve"
+    "rgb": "225 166 121"
+    "blend": "0.72"
+    "taper_f": "1"
+    "line_w": "31"
+    "line_t": "0.89"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "275.387 309.296 87.3653"
+    "s": "31 33.3182 31"
+  }
+  {
+    "type": "jt"
+    "op": "revolve"
+    "rgb": "225 166 121"
+    "blend": "0.72"
+    "taper_f": "1"
+    "taper_c": "1"
+    "line_w": "31"
+    "line_t": "0.89"
+    "r": "0.48481 -0.87462 0 0.87462 0.48481 0 0 0 1"
+    "t": "382.569 304.773 88.6313"
+    "s": "31 66.5444 31"
+  }
+  {
+    "type": "bezier"
+    "op": "revolve"
+    "rgb": "152 121 225"
+    "blend": "1.00"
+    "line_w": "3"
+    "cpn"   : "3"
+    "v1": "46.7568 13.6572 0"
+    "v2": "24.5674 112.006 0"
+    "r": "1 0 0 0 0 1 0 -1 0"
+    "t": "-327.203 -77.172 11.9998"
+    "s": "23.3784 56.003 23.3784"
+  }
+  {
+    "type": "bezier"
+    "op": "revolve"
+    "rgb": "152 121 225"
+    "blend": "1.00"
+    "line_w": "3"
+    "cpn"   : "4"
+    "v1": "47.1742 1.97522 0"
+    "v2": "29.0068 67.8888 0"
+    "v3": "73.7265 131.722 0"
+    "r": "1 0 0 0 0 1 0 -1 0"
+    "t": "-341.023 -208.021 11.9997"
+    "s": "36.8633 65.861 36.8633"
+  }
+  {
+    "type": "triangle"
+    "rgb": "186 122 111"
+    "blend": "0.65"
+    "bevel": "8.39"
+    "cpn"   : "4"
+    "cp3d"   : "1"
+    "v1": "123.934 -5.87432 -9.9001"
+    "v2": "31.9969 101.067 13.4463"
+    "v3": "66.3762 47.0197 99.3701"
+    "r": "0.224951 0.97437 0 -0.97437 0.224951 0 0 0 1"
+    "t": "-143.025 288.653 33.8015"
+    "s": "70.357 61.8607 63.0251"
+  }
+  {
+    "type": "triangle"
+    "rgb": "186 122 111"
+    "blend": "1.00"
+    "round": "5.00"
+    "bevel": "12.00"
+    "cpn"   : "4"
+    "v1": "115.848 40.3154 0"
+    "v2": "87.1635 72.8789 0"
+    "v3": "9.2695 87.1677 0"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-381.523 275.152 36.1639"
+    "s": "69.924 55.5839 14.7019"
+  }
+  {
+    "type": "line"
+    "op": "revolve"
+    "rgb": "152 121 225"
+    "blend": "1.00"
+    "line_w": "3"
+    "cpn"   : "4"
+    "v1": "47.1742 1.97522 0"
+    "v2": "68.3307 65.6229 0"
+    "v3": "55.2117 86.2898 0"
+    "r": "1 0 0 0 0 1 0 -1 0"
+    "t": "-332.493 -367.087 11.9997"
+    "s": "34.1654 43.1449 34.1654"
+  }
+  {
+    "type": "line"
+    "rgb": "134 193 102"
+    "blend": "0.72"
+    "line_w": "11"
+    "line_t": "0.86"
+    "cpn"   : "4"
+    "cp3d"   : "1"
+    "v1": "103.259 -27.1136 -27.6716"
+    "v2": "33.445 -25.9005 -45.0142"
+    "v3": "43.2235 -103.052 -8.07178"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "-85.6751 -330.22 66.177"
+    "s": "51.6295 51.526 33.5071"
+  }
+  {
+    "type": "cube"
+    "op": "lmz"
+    "rgb": "186 122 111"
+    "blend": "0.35"
+    "cone%": "0.92"
+    "bevel%": "0.167615"
+    "r": "1 0 0 0 1 0 0 0 1"
+    "t": "103.307 355.669 66.3865"
+    "s": "37.9821 57.3678 56.5561"
+  }
+  {
+    "type": "triangle"
+    "rgb": "186 122 111"
+    "blend": "0.65"
+    "bevel": "8.39"
+    "cpn"   : "3"
+    "cp3d"   : "1"
+    "v1": "101.186 -19.5941 -38.654"
+    "v2": "155.111 15.4358 97.1493"
+    "r": "0.224951 0.97437 0 -0.97437 0.224951 0 0 0 1"
+    "t": "-32.2507 286.425 62.5554"
+    "s": "85.9455 25.9049 76.2916"
+  }
+  ]
+]
+"palette" :
+[
+  "0 0 0"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "215 84 85"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "247 92 47"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "253 160 149"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "134 193 102"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "102 186 183"
+  "38 135 133"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "129 199 212"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "88 90 141"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "224 131 174"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "221 221 221"
+  "198 198 198"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "75 75 75"
+  "68 68 68"
+  "75 75 75"
+  "0 0 0"
+]