Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sphere #1

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [ "master" ]
pull_request:

env:
CARGO_TERM_COLOR: always

Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Kodama

Kodama is a language for describing 3D models.
There is no spec, or CLI as of now. As requirements are evaluated, the spec will be refined to fit users' desires.

## Mission Statement

Due to the large complexity of 3D modelling tools such as Blender, it is difficult for beginners to enter the space.
It has been observed in fields such as programming that there is a clear progression curve in terms of the usability of tools.

For example in programming languages, we do not encourage beginners to learn rust or haskell.
We suggest languages such as Python, JavaScript, or C.
This is because these languages provide a simple interface for writing programs, with the option of using more complicated features of the language.

This is contrasted with languages such as rust, or haskell, where the minimum level of knowledge for writing programs is much higher.

This is often called the skill floor.

It is not common for applications to have a high skill floor. This is often because they use a GUI for all operations.
GUI applications need to have all options visible, or accessible in a short amount of clicks, or keyboard shortcuts.

As applications scale in complexity, their GUIs have to reflect that.

It is often necessary to allow users to create interesting things.

This is in contrast to programming languages, which can be seen as a subset of text based applications.
All the UI of a programming language is captured in a set of UTF-8 encoded keywords, and grammar.

This then means that no bespoke tools are mandatory for writing programs.
This README was written in vim, but it could easily have been written in vscode, notepad, or even by hovering a magnet over my laptop.

Kodama aims to become a tool for users to describe 3D models in text.
This allows beginners to only be exposed to the parts of the language they need for their specific case, with any extra features only a google or docs search away.
9 changes: 9 additions & 0 deletions annoying.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cargo clippy -- \
-Wclippy::all \
-Wclippy::restriction \
-Wclippy::correctness \
-Wclippy::pedantic \
-Wclippy::nursery \
-Wclippy::cargo \
-Wclippy::suspicious \
-Wclippy::perf
211 changes: 211 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
use std::f32::consts::PI;

#[derive(Clone, Copy, Debug)]
struct Point {
x: f32,
y: f32,
z: f32,
}

impl Point {
const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn to_obj_string(self) -> String {
format!("v {0} {1} {2}", self.x, self.y, self.z)
}
}

#[derive(Clone)]
struct Face {
points: Vec<Point>,
indexes: Vec<u32>,
}

impl Face {
pub fn new(points: Vec<Point>, indexes: Vec<u32>) -> Self {
Self { points, indexes }
}
pub fn to_obj_string(self) -> String {
let mut result = String::from("f ");

result.push_str(
&self
.indexes
.clone()
.into_iter()
.map(|i: u32| -> String {
format!("-{0}", (self.points.len() as u64 - u64::from(i)))
})
.collect::<Vec<String>>()
.join(" "),
);
result
}
}

fn vertex_string(points: Vec<Point>) -> String {
points
.into_iter()
.map(Point::to_obj_string)
.collect::<Vec<String>>()
.join("\n")
}


fn render_obj(points: Vec<Point>, faces: Vec<Face>) -> String {
format!(
r#"{0}
{1}
"#,
vertex_string(points.to_vec()),
faces
.into_iter()
.map(Face::to_obj_string)
.collect::<Vec<String>>()
.join("\n")
)
}

fn sphere(origin: Point, radius: f32, _detail: u32) -> Result<String, String> {
// top and bottom point
// create vertical "strips" down the sphere,
// rotating around the Z axis
// Create faces procedurally

let points = [
//top
Point::new(origin.x, origin.y + (radius * (2.0 * PI).sin()), origin.z),
//bottom
Point::new(origin.x, origin.y + (radius * (-2.0 * PI).sin()), origin.z),
//north
Point::new(origin.x, origin.y, origin.z + (radius * (2.0 * PI).sin())),
//south
Point::new(origin.x, origin.y, origin.z + (radius * (-2.0 * PI).sin())),
//east
Point::new(origin.x + (radius * (0.0_f32).cos()), origin.y, origin.z),
//west
Point::new(origin.x + (radius * (PI).cos()), origin.y, origin.z),
]
.to_vec();

let faces = [
// top, north, east
Face::new(points.clone(), vec![0, 2, 4]),
// top, north, west
Face::new(points.clone(), vec![0, 2, 5]),
// top, south, east
Face::new(points.clone(), vec![0, 3, 4]),
// top, south, west
Face::new(points.clone(), vec![0, 3, 5]),
// bottom, north, east
Face::new(points.clone(), vec![1, 2, 4]),
// bottom, north, west
Face::new(points.clone(), vec![1, 2, 5]),
// bottom, south, east
Face::new(points.clone(), vec![1, 3, 4]),
// bottom, south, west
Face::new(points.clone(), vec![1, 3, 5]),
];

Ok(render_obj(points, faces.to_vec()))
}

fn cone(origin: Point, _detail: i32, _radius: f32) -> String {
let points = [
Point::new(origin.x, origin.y + (PI / 3.0).sin(), origin.z),
Point::new(origin.x, origin.y, origin.z + 0.0_f32.cos()),
Point::new(
origin.x + (4.0 * PI / 3.0).sin(),
origin.y,
(2.0 * -PI / 3.0).cos(),
),
Point::new(
origin.x + (2.0 * PI / 3.0).sin(),
origin.y,
(2.0 * -PI / 3.0).cos(),
),
];

let faces = [
Face::new(points.to_vec(), vec![1, 2, 3]),
Face::new(points.to_vec(), vec![0, 2, 3]),
Face::new(points.to_vec(), vec![0, 1, 3]),
Face::new(points.to_vec(), vec![0, 2, 1]),
];

render_obj(points.to_vec(), faces.to_vec())
}

fn cuboid(origin: Point, sx: f32, sy: f32, sz: f32) -> Result<String, String> {
println!("GENERATING CUBOID");
if sx <= 0.0 || sy <= 0.0 || sz <= 0.0 {
return Err(
String::from("could not generate cuboid, side length less than or equal to zero"),
);
}
let points = [
Point::new(origin.x, origin.y + sy, origin.z + sz),
Point::new(origin.x, origin.y, origin.z + sz),
Point::new(origin.x + sx, origin.y, origin.z + sz),
Point::new(origin.x + sx, origin.y + sy, origin.z + sz),
Point::new(origin.x, origin.y + sy, origin.z),
Point::new(origin.x, origin.y, origin.z),
Point::new(origin.x + sx, origin.y, origin.z),
Point::new(origin.x + sx, origin.y + sy, origin.z),
];

let faces = [
Face::new(points.to_vec(), vec![0, 1, 2, 3]),
Face::new(points.to_vec(), vec![7, 6, 5, 4]),
Face::new(points.to_vec(), vec![4, 5, 1, 0]),
Face::new(points.to_vec(), vec![3, 7, 4, 0]),
Face::new(points.to_vec(), vec![3, 2, 6, 7]),
Face::new(points.to_vec(), vec![6, 2, 1, 5]),
];

Ok(render_obj(points.to_vec(), faces.to_vec()))
}

fn cube(origin: Point, size: f32) -> Result<String, String> {
println!("GENERATING CUBE");
if size <= 0.0 {
return Err("ERROR: cannot generate cube of size less than zero".to_string());
}
cuboid(origin, size, size, size)
}

pub fn compile(data: &str) -> String {
let mut result = String::new();

let lines = data.split('\n');
for line in lines {
let tokens: Vec<&str> = line.split(' ').collect();
match *tokens.first().unwrap() {
"cube" => result.push_str(
&cube(
Point::new(0.0, 0.0, 0.0),
tokens[1].parse::<f32>().expect("invalid value given"),
)
.unwrap(),
),
"cuboid" => result.push_str(
&cuboid(
Point::new(0.0, 0.0, 0.0),
tokens[1].parse::<f32>().expect("non numeric value given"),
tokens[2].parse::<f32>().expect("non numeric value given"),
tokens[3].parse::<f32>().expect("non numeric value given"),
)
.unwrap(),
),
"cone" => result.push_str(&cone(
Point::new(0.0, 0.0, 0.0),
0,
tokens[1].parse::<f32>().expect("non numeric value given"),
)),
"sphere" => result.push_str(&sphere(Point::new(0.0, 0.0, 0.0), 10.0, 10).unwrap()),
&_ => println!("{} not supported", tokens[0]),
}
}
result
}
Loading