diff --git a/prod/tsm.png b/prod/tsm.png
new file mode 100644
index 0000000..51f802e
Binary files /dev/null and b/prod/tsm.png differ
diff --git a/scenes/smolgalaxy.lua b/scenes/smolgalaxy.lua
index 845a84d..57b644c 100644
--- a/scenes/smolgalaxy.lua
+++ b/scenes/smolgalaxy.lua
@@ -70,7 +70,7 @@ local function randomtorus()
 	local thickness = util.randab(radius/4, radius)
 	local orientation = randomorientation()
 
-	return obj.affinetransform(obj.torus(pos, radius, thickness), orientation, vec3.O)
+	return util.transform(obj.torus(pos, radius, thickness), orientation)
 end
 
 local function randomcuboid()
@@ -86,13 +86,12 @@ local function randomcylinder()
 	local height = util.randab(0, SCALE)
 	local orientation = randomorientation()
 
-	return obj.affinetransform(
+	return util.transform(
 		obj.intersection(
 			obj.cylinder(pos, radius),
 			obj.cuboid(pos, vec3.new(radius, height, radius))
 		),
-		orientation,
-		vec3.O
+		orientation
 	)
 end
 
diff --git a/scenes/tsm.lua b/scenes/tsm.lua
new file mode 100644
index 0000000..5c51a8e
--- /dev/null
+++ b/scenes/tsm.lua
@@ -0,0 +1,157 @@
+-- Scene developed by TSM71
+-- Also this took 30 hours to render
+
+local COLOR_BLACK = colorvec.new(0, 0, 0, 0)
+
+local GRAY = material.newfromdiagonal(
+	COLOR_BLACK,
+	colorvec.new(0.5, 0.5, 0.5, 0.5),
+	surfacetype.DIFFUSE
+)
+local CYAN = material.newfromdiagonal(
+	COLOR_BLACK,
+	colorvec.new(0, 1, 1, 0),
+	surfacetype.DIFFUSE
+)
+local PINK = material.newfromdiagonal(
+	COLOR_BLACK,
+	colorvec.new(1, .5, .5, 0),
+	surfacetype.DIFFUSE
+)
+local RED = material.newfromdiagonal(
+	COLOR_BLACK,
+	colorvec.new(1, 0, 0, 0),
+	surfacetype.DIFFUSE
+)
+local MAGENTA = material.newfromdiagonal(
+	COLOR_BLACK,
+	colorvec.new(1, 0, 1, 0),
+	surfacetype.DIFFUSE
+)
+local MIRROR = material.newfromdiagonal(
+	COLOR_BLACK,
+	colorvec.new(1, 1, 1, 1),
+	surfacetype.REFLECTIVE
+)
+local FLUORESCENT = material.new(
+	COLOR_BLACK,
+	colormat.new(
+		.25, 0, 0, .25,
+		0, .25, 0, .75,
+		0, 0, .25, 0,
+		0, 0, 0, 0
+	),
+	surfacetype.DIFFUSE
+)
+
+--[[
+obj.transformaround = function(a, b, c)
+	return util.translate(
+		util.transform(
+			util.translate(
+				a,
+				-c
+			),
+			b
+		),
+		c
+	)
+end
+--]]
+--obj.transformaround = function(a, _b, _c) return a end
+--obj.affinetransform = function(a, _b, _c) return a end
+--obj.smoothunion = function(a, b, _r) return obj.union(a, b) end
+---[[
+util.stacktransforms = function(list)
+	--do return list[1] end
+	--do return list[#list] end
+	local n = #list
+	local a = mat3.I
+	--for i=n, 1, -1 do
+	for i=1, n do
+		a = list[i] * a
+		--a = a * list[i]
+	end
+	return a
+end
+--]]
+
+return obj.withlights(
+	obj.transformaround(
+		obj.withlights(
+			util.union({
+				util.intersection({
+					util.union({
+						obj.withmaterial(
+							obj.negation(
+								util.union({
+									obj.cuboid(vec3.new(0, 0, 0), vec3.new(.5, 1, 4)),
+									obj.cuboid(vec3.new(0, 0, 0), vec3.new(4, 1, .5))
+								})
+							),
+							GRAY
+						),
+						obj.withmaterial(
+							util.intersection({
+								util.union({
+									obj.cuboid(vec3.new(0, 1, 0), vec3.new(.4, .1, 3.9)),
+									obj.cuboid(vec3.new(0, 1, 0), vec3.new(3.9, .1, .4))
+								}),
+								obj.negation(
+									util.union({
+										obj.cuboid(vec3.new(0, 1, 0), vec3.new(.3, 1, 3.8)),
+										obj.cuboid(vec3.new(0, 1, 0), vec3.new(3.8, 1, .3))
+									})
+								)
+							}),
+							FLUORESCENT
+						),
+						obj.smoothunion(
+							obj.withmaterial(
+								obj.sphere(vec3.new(0, .75, 0), .25),
+								CYAN
+							),
+							obj.withmaterial( -- withdynamicmaterial
+								obj.sphere(vec3.new(-.5, .5, 0), .25),
+								MAGENTA --dynamicmaterial.missingtexture(vec3.new(-.5, .5, 0))
+							),
+							.5
+						)
+					}),
+					obj.withmaterial(
+						obj.negation(
+							obj.cuboid(vec3.new(0, 0, 0), vec3.new(2, .25, 2))
+						),
+						PINK
+					)
+				}),
+				obj.withmaterial(
+					obj.cylinder(vec3.new(1, 0, 0), .375),
+					RED
+				),
+				util.transform(
+					obj.withmaterial(
+						obj.torus(vec3.new(0, 0, -1), .375, .125),
+						MIRROR
+					),
+					transform.SWAPYZ
+				)
+			}),
+			{
+				light.new(vec3.new(.45, 1-.2, -.45), colorvec.new(0, 0, 0, .1)),
+				--light.new(vec3.new(.45, 1-.2, -.45), colorvec.new(0, 0, 0, 1)),
+			}
+		),
+		util.stacktransforms({
+			transform.scalexyz(1, -1, 1),
+			transform.rotatez(10*math.pi/180),
+		}),
+		vec3.new(0, 0, -1.5)
+	),
+	{
+		light.new(vec3.new(0, .9375, 0),  colorvec.new(.5, .25, 0, 0)),
+		light.new(vec3.new(0, .9375, -.625),  colorvec.new(0, 0, 1, 0)),
+		--light.new(vec3.new(0, .9375, 0),  colorvec.new(5, 2.5, 0, 0)),
+		--light.new(vec3.new(0, .9375, -.625),  colorvec.new(0, 0, 10, 0)),
+	}
+)
\ No newline at end of file
diff --git a/src/consts.rs b/src/consts.rs
index 082641a..0c6baed 100644
--- a/src/consts.rs
+++ b/src/consts.rs
@@ -20,21 +20,21 @@ pub const ANGLE_POWER: f64 = 2.;
 //pub const IMG_WIDTH: usize = 480;
 //pub const IMG_WIDTH: usize = 1280;
 //pub const IMG_WIDTH: usize = 1080;
-//pub const IMG_WIDTH: usize = 1920;
-pub const IMG_WIDTH: usize = 4961;
+pub const IMG_WIDTH: usize = 1920;
+//pub const IMG_WIDTH: usize = 4961;
 //pub const IMG_HEIGHT: usize = 480;
 //pub const IMG_HEIGHT: usize = 720;
-//pub const IMG_HEIGHT: usize = 1080;
-pub const IMG_HEIGHT: usize = 3508;
+pub const IMG_HEIGHT: usize = 1080;
+//pub const IMG_HEIGHT: usize = 3508;
 pub const IMG_DIM: usize = if IMG_HEIGHT > IMG_WIDTH { IMG_HEIGHT } else { IMG_WIDTH };
 pub const IMG_SIZE: usize = IMG_WIDTH * IMG_HEIGHT;
 pub const IMG_BYTE_SIZE: usize = IMG_SIZE * 3;
 
-pub const SUPERSAMPLING: usize = 1;
-//pub const SUPERSAMPLING: usize = 2;
-pub const RAYS_PER_PIXEL: usize = 1;
+//pub const SUPERSAMPLING: usize = 1;
+pub const SUPERSAMPLING: usize = 2;
+//pub const RAYS_PER_PIXEL: usize = 1;
 //pub const RAYS_PER_PIXEL: usize = 50;
-//pub const RAYS_PER_PIXEL: usize = 500;
+pub const RAYS_PER_PIXEL: usize = 500;
 //pub const MAX_BOUNCES: u32 = 1;
 //pub const MAX_BOUNCES: u32 = 4;
 //pub const MAX_BOUNCES: u32 = 8;
diff --git a/src/lua/mat3.rs b/src/lua/mat3.rs
index cb763ba..10d11d7 100644
--- a/src/lua/mat3.rs
+++ b/src/lua/mat3.rs
@@ -7,7 +7,7 @@ impl UserData for Mat3 {
         methods.add_meta_method(MetaMethod::Sub, |ctx, a: &Self, b: Self| Ok(*a-b));
         methods.add_meta_method(MetaMethod::Mul, |ctx, a: &Self, b: Self| Ok(*a*b));
         methods.add_meta_method(MetaMethod::Unm, |ctx, a: &Self, b: ()| Ok(-*a));
-        methods.add_meta_method(MetaMethod::Mul, |ctx, a: &Self, b: f64| Ok(*a*b));
+        //methods.add_meta_method(MetaMethod::Mul, |ctx, a: &Self, b: f64| Ok(*a*b)); //TODO find a way to make it cohabit
         methods.add_meta_method(MetaMethod::Div, |ctx, a: &Self, b: f64| Ok(*a/b));
 
         methods.add_method("a", |ctx, x, ()| Ok(x.a()));
diff --git a/src/lua/obj.rs b/src/lua/obj.rs
index f3eb2dc..b74369a 100644
--- a/src/lua/obj.rs
+++ b/src/lua/obj.rs
@@ -54,6 +54,10 @@ pub fn obj<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result
(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
@@ -26,11 +26,25 @@ pub fn transform<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result| {
             let mut acc = I3;
             for trans in transforms.iter().rev().cloned() {
-                acc = trans * I3;
+                acc = acc * trans;
             }
             Ok(acc)
         }
diff --git a/src/lua/util.lua b/src/lua/util.lua
index e1b4730..a3505d6 100644
--- a/src/lua/util.lua
+++ b/src/lua/util.lua
@@ -65,7 +65,7 @@ function M.transform(object, mat)
 	return obj.affinetransform(object, mat, vec3.O)
 end
 function M.translate(object, vec)
-	return obj.affinetransform(object, mat3.O, vec)
+	return obj.affinetransform(object, mat3.I, vec)
 end
 
 return M
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index c271ce3..a8ab5c5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -85,9 +85,10 @@ fn default_scene3() -> Scene {
 
 fn main() {
     // get scene and camera
-    let scene = scene_from_file("scenes/randomspheres.lua".to_owned()).unwrap();
+    let scene = scene_from_file("scenes/tsm.lua".to_owned()).unwrap();
     //let scene = default_scene3();
-    let cam = default_cam();
+    let cam = Cam::new(Z * -1.5, Z, f64::sqrt(2.));
+    //let cam = default_cam();
 
     // get stats on the scene we're about to render
     let total_rpp = (RAYS_PER_PIXEL * SUPERSAMPLING * SUPERSAMPLING) as u64;
diff --git a/src/object/mod.rs b/src/object/mod.rs
index 10c082f..d89c77c 100644
--- a/src/object/mod.rs
+++ b/src/object/mod.rs
@@ -8,6 +8,7 @@ use std::vec::Vec;
 mod sphere;
 mod plane;
 mod union;
+mod smoothunion;
 mod intersection;
 mod exclusion;
 mod negation;
@@ -64,6 +65,7 @@ impl ObjClone for T {
 pub use sphere::Sphere;
 pub use plane::Plane;
 pub use union::Union;
+pub use smoothunion::SmoothUnion;
 pub use intersection::Intersection;
 pub use exclusion::Exclusion;
 pub use negation::Negation;
diff --git a/src/object/smoothunion.rs b/src/object/smoothunion.rs
new file mode 100644
index 0000000..e4971e5
--- /dev/null
+++ b/src/object/smoothunion.rs
@@ -0,0 +1,42 @@
+use crate::object::Obj;
+use crate::light::Light;
+use crate::structs::Vec3;
+use std::vec;
+use crate::material::Material;
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct SmoothUnion {
+    a: A,
+    b: B,
+    r: f64
+}
+
+impl SmoothUnion {
+    pub fn new(a: A, b: B, r: f64) -> SmoothUnion {
+        SmoothUnion { a, b, r }
+    }
+}
+
+impl Obj for SmoothUnion {
+    fn distance_to(&self, point: Vec3) -> f64 {
+        let d1 = self.a.distance_to(point);
+        let d2 = self.b.distance_to(point);
+        let h = f64::max(self.r - f64::abs(d1-d2),0.);
+        d1.min(d2) - h*h*0.25 / self.r
+    }
+    fn material_at(&self, point: Vec3) -> Material {
+        if self.a.distance_to(point) < self.b.distance_to(point) {
+            self.a.material_at(point)
+        } else {
+            self.b.material_at(point)
+        }
+    }
+    fn get_lights(&self) -> vec::Vec {
+        let mut l = self.a.get_lights();
+        l.extend(self.b.get_lights());
+        l
+    }
+    fn node_count(&self) -> u32 {
+        self.a.node_count() + self.b.node_count() + 1
+    }
+}
diff --git a/src/object/transform.rs b/src/object/transform.rs
index 499f962..9221902 100644
--- a/src/object/transform.rs
+++ b/src/object/transform.rs
@@ -14,10 +14,10 @@ pub struct AffineTransform {
 
 impl AffineTransform {
     pub fn new(obj: T, transform: Mat3, translate: Vec3) -> AffineTransform {
-        AffineTransform { obj, transform, transform_inv: transform.invert(), translate, scale: transform.det().cbrt() }
+        AffineTransform { obj, transform, transform_inv: transform.invert(), translate, scale: transform.det().abs().cbrt() }
     }
     pub fn new_linear(obj: T, transform: Mat3) -> AffineTransform {
-        AffineTransform { obj, transform, transform_inv: transform.invert(), translate: O, scale: transform.det().cbrt() }
+        AffineTransform { obj, transform, transform_inv: transform.invert(), translate: O, scale: transform.det().abs().cbrt() }
     }
     pub fn new_translate(obj: T, translate: Vec3) -> AffineTransform {
         AffineTransform { obj, transform: I3, transform_inv: I3, translate, scale: 1. }
@@ -59,6 +59,37 @@ pub const fn scale_x(k: f64) -> Mat3 { scale_xyz(k, 1., 1.) }
 pub const fn scale_y(k: f64) -> Mat3 { scale_xyz(1., k, 1.) }
 pub const fn scale_z(k: f64) -> Mat3 { scale_xyz(1., 1., k) }
 
+pub fn rotate_x(a: f64) -> Mat3 {
+    let c = a.cos();
+    let s = a.sin();
+    Mat3::new(
+        1., 0., 0.,
+        0.,  c, -s,
+        0.,  s,  c
+    )
+}
+pub fn rotate_y(a: f64) -> Mat3 {
+    let c = a.cos();
+    let s = a.sin();
+    Mat3::new(
+         c, 0.,  s,
+        0., 1., 0.,
+        -s, 0.,  c
+    )
+}
+pub fn rotate_z(a: f64) -> Mat3 {
+    let c = a.cos();
+    let s = a.sin();
+    Mat3::new(
+         c, -s, 0.,
+         s,  c, 0.,
+        0., 0., 1.
+    )
+}
+pub fn rotate_xyz(x: f64, y: f64, z: f64) -> Mat3 {
+    rotate_z(z) * rotate_y(y) * rotate_x(x)
+}
+
 impl Obj for AffineTransform {
     fn distance_to(&self, point: Vec3) -> f64 {
         self.obj.distance_to(self.apply_rev(point)) * self.scale
@@ -81,4 +112,50 @@ impl Obj for AffineTransform {
     fn node_count(&self) -> u32 {
         self.obj.node_count() + 1
     }
-}
\ No newline at end of file
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct TransformAround {
+    obj: T,
+    transform: Mat3,
+    transform_inv: Mat3,
+    center: Vec3,
+    scale: f64
+}
+
+impl TransformAround {
+    pub fn new(obj: T, transform: Mat3, center: Vec3) -> TransformAround {
+        TransformAround { obj, transform, transform_inv: transform.invert(), center, scale: transform.det().abs().cbrt() }
+    }
+
+    fn apply_rev(&self, point: Vec3) -> Vec3 {
+        self.transform_inv * (point + self.center) - self.center
+    }
+    fn apply_fwd(&self, point: Vec3) -> Vec3 {
+        self.transform * (point - self.center) + self.center
+    }
+}
+
+impl Obj for TransformAround {
+    fn distance_to(&self, point: Vec3) -> f64 {
+        self.obj.distance_to(self.apply_rev(point)) * self.scale
+    }
+    fn normal_at(&self, point: Vec3) -> Vec3 {
+        self.apply_fwd(self.obj.normal_at(self.apply_rev(point))).unit()
+    }
+    fn material_at(&self, point: Vec3) -> Material {
+        self.obj.material_at(self.apply_rev(point))
+    }
+    fn get_lights(&self) -> Vec {
+        self.obj
+            .get_lights()
+            .into_iter()
+            .map(|light| {
+                Light::new(self.apply_fwd(light.pos()), light.color())
+            })
+            .collect()
+    }
+    fn node_count(&self) -> u32 {
+        self.obj.node_count() + 1
+    }
+}
diff --git a/src/structs/mat3.rs b/src/structs/mat3.rs
index aa3af00..6ea1e0f 100644
--- a/src/structs/mat3.rs
+++ b/src/structs/mat3.rs
@@ -137,7 +137,7 @@ impl Mul for Mat3 {
 
             d: self.d*other.a + self.e*other.d + self.f*other.g,
             e: self.d*other.b + self.e*other.e + self.f*other.h,
-            f: self.d*other.c + self.e*other.f + self.e*other.i,
+            f: self.d*other.c + self.e*other.f + self.f*other.i,
 
             g: self.g*other.a + self.h*other.d + self.i*other.g,
             h: self.g*other.b + self.h*other.e + self.i*other.h,
diff --git a/test b/test
index 05bc9e0..bbe9dfb 100755
--- a/test
+++ b/test
@@ -1,7 +1,8 @@
 #!/bin/bash
+[ -z "$SEED" ] && export SEED=0
 clear
 cargo build --release && \
 	clear && \
-	time SEED=0 target/release/rmarcher && \
+	time target/release/rmarcher && \
 	printf '\n' && \
 	kitty +kitten icat --align=left a.png