Browse Source

scene_parser

Inderjit Gill 2 months ago
parent
commit
355e3862b5
7 changed files with 589 additions and 386 deletions
  1. 15
    15
      samplescene.rts
  2. 12
    0
      src/error.rs
  3. 27
    36
      src/main.rs
  4. 191
    161
      src/parser.rs
  5. 2
    1
      src/picture.rs
  6. 0
    173
      src/scene.rs
  7. 342
    0
      src/scene_parser.rs

+ 15
- 15
samplescene.rts View File

@@ -1,4 +1,18 @@
1
-;; rts = ray trace scene
1
+;; default picture parameters that can be overridden on the command line
2
+(picture width: 100
3
+         height: 100
4
+         samples: 1
5
+         output: "foo.png")
6
+
7
+;; aspect and focus-dist are optional and will be calculated if omitted
8
+;; (aspect from picture parameters, focus-dist from look-from and look-at)
9
+;;
10
+(camera look-from: [0.0 0.0 0.0]
11
+        look-at: [0.0 0.0 -1.0]
12
+        look-up: [0.0 1.0 0.0]
13
+        vfov: 90
14
+        aperture: 0)
15
+
2 16
 
3 17
 (sphere center: [0.0 0.0 -1.0]
4 18
         radius: 0.5
@@ -17,17 +31,3 @@
17 31
         radius: 0.5
18 32
         material: (metal albedo: [0.8 0.8 0.8]
19 33
                          fuzz: 0.3))
20
-
21
-;; default picture parameters that can be overridden on the command line
22
-(picture width: 100
23
-         height: 100
24
-         samples: 1
25
-         output: "foo.png")
26
-
27
-(camera look-from: [0.0, 0.0, 0.0]
28
-        look-at: [0.0, 0.0, -1.0]
29
-        vup: [0.0, 1.0, 0.0]
30
-        vfov: 90
31
-        aspect: 1                       ; leave out == calculate from picture parameters
32
-        aperture: 0                     ; leave out == calculate from look-from, look-at
33
-        focus-dist: 1)

+ 12
- 0
src/error.rs View File

@@ -25,6 +25,12 @@ pub enum RayTracerError {
25 25
     #[fail(display = "Parser handled token")]
26 26
     ParserHandledToken,
27 27
 
28
+    #[fail(display = "SceneParser")]
29
+    SceneParser,
30
+
31
+    #[fail(display = "parse int error")]
32
+    ParseIntError(#[cause] std::num::ParseIntError),
33
+
28 34
     // #[fail(display = "No first image")] NoFirstImage,
29 35
 }
30 36
 
@@ -39,3 +45,9 @@ impl From<image::ImageError> for RayTracerError {
39 45
         RayTracerError::ImageError(e)
40 46
     }
41 47
 }
48
+
49
+impl From<std::num::ParseIntError> for RayTracerError {
50
+    fn from(e: std::num::ParseIntError) -> RayTracerError {
51
+        RayTracerError::ParseIntError(e)
52
+    }
53
+}

+ 27
- 36
src/main.rs View File

@@ -2,7 +2,6 @@
2 2
 #![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names))]
3 3
 #![feature(duration_as_u128)]
4 4
 
5
-#[macro_use]
6 5
 extern crate clap;
7 6
 extern crate env_logger;
8 7
 #[macro_use]
@@ -23,6 +22,7 @@ mod scene;
23 22
 mod shape;
24 23
 mod types;
25 24
 mod parser;
25
+mod scene_parser;
26 26
 
27 27
 use cgmath::prelude::*;
28 28
 use clap::{App, Arg};
@@ -36,7 +36,7 @@ use rng::ThreadLocalRng;
36 36
 use scene::*;
37 37
 use shape::*;
38 38
 use types::*;
39
-use parser::*;
39
+use scene_parser::*;
40 40
 
41 41
 fn main() -> Result<()> {
42 42
     env_logger::init_from_env("RAYTRACER_LOG");
@@ -46,6 +46,12 @@ fn main() -> Result<()> {
46 46
         .author("Inderjit Gill <email@indy.io>")
47 47
         .about("A Ray Tracer")
48 48
         .arg(
49
+            Arg::with_name("scene")
50
+                .short("i")
51
+                .long("scene")
52
+                .help("The input scene to render")
53
+                .takes_value(true),
54
+        ).arg(
49 55
             Arg::with_name("width")
50 56
                 .short("w")
51 57
                 .long("width")
@@ -72,52 +78,37 @@ fn main() -> Result<()> {
72 78
         ).get_matches();
73 79
 
74 80
     // get command line arguments
81
+    if let Some(inp) = matches.value_of("scene") {
82
+        let (scene, mut picture) = parse_scene(inp)?;
75 83
 
76
-    let output;
77
-    if let Some(o) = matches.value_of("output") {
78
-        output = o;
79
-    } else {
80
-        // assume that we have no command line arguments, just generate a default image
81
-        let img = ray_trace_main(
82
-            ScenePreset::Basic,
83
-            Picture {
84
-                width: 200,
85
-                height: 100,
86
-                samples: 10,
87
-            },
88
-        )?;
89
-        img.save("auto.png")?;
90
-        return Ok(());
91
-    }
92
-
93
-    let width = value_t!(matches, "width", u32).unwrap_or(100);
94
-    let height = value_t!(matches, "height", u32).unwrap_or(50);
95
-    let samples = value_t!(matches, "samples", u32).unwrap_or(1);
96
-
97
-    let picture = Picture {
98
-        width,
99
-        height,
100
-        samples,
101
-    };
84
+        if let Some(output) = matches.value_of("output") {
85
+            picture.output = output.to_string();
86
+        }
87
+        if let Some(width) = matches.value_of("width") {
88
+            picture.width = width.parse()?;
89
+        }
90
+        if let Some(height) = matches.value_of("height") {
91
+            picture.height = height.parse()?;
92
+        }
93
+        if let Some(samples) = matches.value_of("samples") {
94
+            picture.samples = samples.parse()?;
95
+        }
102 96
 
103
-    let img = ray_trace_main(ScenePreset::Basic, picture)?;
97
+        let img = ray_trace_main(&scene, &picture)?;
104 98
 
105
-    // save to disk
106
-    img.save(output)?;
99
+        // save to disk
100
+        img.save(picture.output)?;
101
+    }
107 102
 
108 103
     Ok(())
109 104
 }
110 105
 
111
-fn ray_trace_main(scene_preset: ScenePreset, picture: Picture) -> Result<(RgbaImage)> {
106
+fn ray_trace_main(scene: &Scene, picture: &Picture) -> Result<(RgbaImage)> {
112 107
     // create the combined image
113 108
     let mut img: RgbaImage = ImageBuffer::new(picture.width, picture.height);
114
-
115 109
     let now = Instant::now();
116
-
117 110
     let mut rng = ThreadLocalRng::new();
118 111
 
119
-    let scene = get_scene(scene_preset, picture, &mut rng);
120
-
121 112
     for y in 0..picture.height {
122 113
         if y % (picture.height / 10) == 0 {
123 114
             println!("{}% complete", (100.0 / picture.height as f32) * y as f32);

+ 191
- 161
src/parser.rs View File

@@ -1,4 +1,4 @@
1
-use error::{Result, RayTracerError};
1
+use error::{RayTracerError, Result};
2 2
 
3 3
 #[derive(Debug, PartialEq)]
4 4
 pub enum Node {
@@ -8,7 +8,6 @@ pub enum Node {
8 8
     Name(String),
9 9
     Label(String),
10 10
     String(String),
11
-    Comment(String),
12 11
     End,
13 12
 }
14 13
 
@@ -16,7 +15,7 @@ pub enum Node {
16 15
 enum Token<'a> {
17 16
     Colon,
18 17
     Comment(&'a str),
19
-    DoubleQuote,
18
+    String(&'a str),
20 19
     Name(&'a str),
21 20
     Number(&'a str),
22 21
     ParenEnd,
@@ -46,8 +45,8 @@ pub fn parse(s: &str) -> Result<Vec<Node>> {
46 45
                     _ => res.push(nar.node),
47 46
                 }
48 47
                 tokens = nar.tokens;
49
-            },
50
-            Err(e) => return Err(e)
48
+            }
49
+            Err(e) => return Err(e),
51 50
         }
52 51
     }
53 52
 
@@ -75,7 +74,7 @@ struct Lexer<'a> {
75 74
 
76 75
 impl<'a> Lexer<'a> {
77 76
     pub fn new(input: &str) -> Lexer {
78
-        Lexer{
77
+        Lexer {
79 78
             input: input,
80 79
             cur_pos: 0,
81 80
         }
@@ -91,17 +90,17 @@ impl<'a> Lexer<'a> {
91 90
                 '[' => Ok((Token::SquareBracketStart, 1)),
92 91
                 ']' => Ok((Token::SquareBracketEnd, 1)),
93 92
                 ':' => Ok((Token::Colon, 1)),
94
-                '"' => Ok((Token::DoubleQuote, 1)),
93
+                '"' => eat_string(&self.input[ind..]),
95 94
                 ';' => eat_comment(&self.input[ind..]),
96
-                '-' | '0' ... '9' => eat_number(&self.input[ind..]),
95
+                '-' | '0'...'9' => eat_number(&self.input[ind..]),
97 96
                 ch if ch.is_whitespace() => eat_whitespace(&self.input[ind..]),
98 97
                 _ if is_name(ch) => eat_name(&self.input[ind..]),
99
-                ch => Err(RayTracerError::ParserInvalidChar(ch))
98
+                ch => Err(RayTracerError::ParserInvalidChar(ch)),
100 99
             };
101 100
 
102 101
             let (tok, size) = match res {
103 102
                 Ok(v) => v,
104
-                Err(kind) => return Err(kind)
103
+                Err(kind) => return Err(kind),
105 104
             };
106 105
 
107 106
             self.cur_pos += size as u32;
@@ -121,10 +120,10 @@ fn is_name(ch: char) -> bool {
121 120
 
122 121
 fn is_symbol(ch: char) -> bool {
123 122
     match ch {
124
-        '+' | '-' | '*' | '/' | '=' | '!' | '@' |
125
-        '#' | '$' | '%' | '^' | '&' | '<' | '>' |
126
-        '?' | '.' => true,
127
-        _ => false
123
+        '+' | '-' | '*' | '/' | '=' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '<' | '>' | '?' => {
124
+            true
125
+        }
126
+        _ => false,
128 127
     }
129 128
 }
130 129
 
@@ -157,7 +156,7 @@ fn eat_number(input: &str) -> Result<(Token, usize)> {
157 156
         match input[1..].chars().next() {
158 157
             Some(ch) if ch.is_digit(10) => (1, &input[1..]),
159 158
             // Actually a name beginning with '-' rather a number
160
-            _ => return eat_name(input)
159
+            _ => return eat_name(input),
161 160
         }
162 161
     } else {
163 162
         (0, input)
@@ -188,13 +187,13 @@ fn eat_number(input: &str) -> Result<(Token, usize)> {
188 187
     }
189 188
 }
190 189
 
191
-fn eat_comment(input: &str) -> Result<(Token, usize)> {
192
-    let rest = &input[1..];     // remove the first character (;)
190
+fn eat_string(input: &str) -> Result<(Token, usize)> {
191
+    let rest = &input[1..]; // remove the first character (\")
193 192
     let mut size = rest.len();
194 193
 
195 194
     for (ind, ch) in rest.char_indices() {
196 195
         match ch {
197
-            '\n' =>{
196
+            '"' => {
198 197
                 size = ind;
199 198
                 break;
200 199
             }
@@ -203,33 +202,25 @@ fn eat_comment(input: &str) -> Result<(Token, usize)> {
203 202
             }
204 203
         }
205 204
     }
206
-    Ok((Token::Comment(&rest[..size]), size + 1))
205
+    Ok((Token::String(&rest[..size]), size + 2))
207 206
 }
208 207
 
209
-fn eat_quote<'a>(t: &'a [Token<'a>]) -> Result<NodeAndRemainder<'a>> {
210
-    let mut tokens = t;
211
-    let mut content: String = "".to_string();
208
+fn eat_comment(input: &str) -> Result<(Token, usize)> {
209
+    let rest = &input[1..]; // remove the first character (;)
210
+    let mut size = rest.len();
212 211
 
213
-    loop {
214
-        match tokens[0] {
215
-            Token::DoubleQuote => return Ok(NodeAndRemainder {
216
-                node: Node::String(content),
217
-                tokens: &tokens[1..],
218
-            }),
212
+    for (ind, ch) in rest.char_indices() {
213
+        match ch {
214
+            '\n' => {
215
+                size = ind;
216
+                break;
217
+            }
219 218
             _ => {
220
-                match eat_token(tokens) {
221
-                    Ok(nar) => {
222
-                        match nar.node {
223
-                            Node::Name(txt) => content = txt,
224
-                            _ => ()
225
-                        }
226
-                        tokens = nar.tokens;
227
-                    },
228
-                    Err(e) => return Err(e)
229
-                }
219
+                continue;
230 220
             }
231 221
         }
232 222
     }
223
+    Ok((Token::Comment(&rest[..size]), size + 1))
233 224
 }
234 225
 
235 226
 // At the first token after a Token::ParenStart
@@ -240,19 +231,19 @@ fn eat_list<'a>(t: &'a [Token<'a>]) -> Result<NodeAndRemainder<'a>> {
240 231
 
241 232
     loop {
242 233
         match tokens[0] {
243
-            Token::ParenEnd => return Ok(NodeAndRemainder {
244
-                node: Node::List(res),
245
-                tokens: &tokens[1..],
246
-            }),
247
-            _ => {
248
-                match eat_token(tokens) {
249
-                    Ok(nar) => {
250
-                        res.push(nar.node);
251
-                        tokens = nar.tokens;
252
-                    },
253
-                    Err(e) => return Err(e)
254
-                }
234
+            Token::ParenEnd => {
235
+                return Ok(NodeAndRemainder {
236
+                    node: Node::List(res),
237
+                    tokens: &tokens[1..],
238
+                })
255 239
             }
240
+            _ => match eat_token(tokens) {
241
+                Ok(nar) => {
242
+                    res.push(nar.node);
243
+                    tokens = nar.tokens;
244
+                }
245
+                Err(e) => return Err(e),
246
+            },
256 247
         }
257 248
     }
258 249
 }
@@ -263,60 +254,66 @@ fn eat_vector<'a>(t: &'a [Token<'a>]) -> Result<NodeAndRemainder<'a>> {
263 254
 
264 255
     loop {
265 256
         match tokens[0] {
266
-            Token::SquareBracketEnd => return Ok(NodeAndRemainder {
267
-                node: Node::Vector(res),
268
-                tokens: &tokens[1..],
269
-            }),
270
-            _ => {
271
-                match eat_token(tokens) {
272
-                    Ok(nar) => {
273
-                        res.push(nar.node);
274
-                        tokens = nar.tokens;
275
-                    },
276
-                    Err(e) => return Err(e)
277
-                }
257
+            Token::SquareBracketEnd => {
258
+                return Ok(NodeAndRemainder {
259
+                    node: Node::Vector(res),
260
+                    tokens: &tokens[1..],
261
+                })
278 262
             }
263
+            _ => match eat_token(tokens) {
264
+                Ok(nar) => {
265
+                    res.push(nar.node);
266
+                    tokens = nar.tokens;
267
+                }
268
+                Err(e) => return Err(e),
269
+            },
279 270
         }
280 271
     }
281 272
 }
282 273
 
283
-fn eat_token<'a>(t: &'a[Token<'a>]) -> Result<NodeAndRemainder<'a>> {
274
+fn eat_token<'a>(t: &'a [Token<'a>]) -> Result<NodeAndRemainder<'a>> {
284 275
     let mut tokens = t;
285 276
 
286 277
     loop {
287 278
         if tokens.is_empty() {
288 279
             return Ok(NodeAndRemainder {
289
-                        node: Node::End,
290
-                        tokens,
291
-                    })
280
+                node: Node::End,
281
+                tokens,
282
+            });
292 283
         }
293 284
 
294 285
         match tokens[0] {
295 286
             // skip whitespace tokens
296 287
             Token::Whitespace(_) => tokens = &tokens[1..],
297 288
             Token::Comment(_) => tokens = &tokens[1..],
298
-            Token::DoubleQuote => return eat_quote(&tokens[1..]),
299
-            Token::Name(txt) =>
289
+            Token::String(txt) => {
290
+                return Ok(NodeAndRemainder {
291
+                    node: Node::String(txt.to_string()),
292
+                    tokens: &tokens[1..],
293
+                })
294
+            }
295
+            Token::Name(txt) => {
300 296
                 if tokens.len() > 1 && tokens[1] == Token::Colon {
301 297
                     return Ok(NodeAndRemainder {
302 298
                         node: Node::Label(txt.to_string()),
303 299
                         tokens: &tokens[2..],
304
-                    })
300
+                    });
305 301
                 } else {
306 302
                     return Ok(NodeAndRemainder {
307 303
                         node: Node::Name(txt.to_string()),
308 304
                         tokens: &tokens[1..],
309
-                    })
310
-                },
305
+                    });
306
+                }
307
+            }
311 308
             Token::Number(txt) => {
312 309
                 return match txt.parse::<f32>() {
313 310
                     Ok(f) => Ok(NodeAndRemainder {
314 311
                         node: Node::Float(f),
315 312
                         tokens: &tokens[1..],
316 313
                     }),
317
-                    Err(_) => Err(RayTracerError::ParserUnableToParseFloat(txt.to_string()))
314
+                    Err(_) => Err(RayTracerError::ParserUnableToParseFloat(txt.to_string())),
318 315
                 }
319
-            },
316
+            }
320 317
             Token::ParenStart => return eat_list(&tokens[1..]),
321 318
             Token::SquareBracketStart => return eat_vector(&tokens[1..]),
322 319
             _ => return Err(RayTracerError::ParserHandledToken),
@@ -330,58 +327,72 @@ mod tests {
330 327
 
331 328
     #[test]
332 329
     fn test_lexer() {
333
-        assert_eq!(tokenize("()").unwrap(),
334
-                   [Token::ParenStart,
335
-                    Token::ParenEnd]);
336
-
337
-        assert_eq!(tokenize("( )").unwrap(),
338
-                   [Token::ParenStart,
339
-                    Token::Whitespace(" "),
340
-                    Token::ParenEnd]);
341
-
342
-        assert_eq!(tokenize("[]").unwrap(),
343
-                   [Token::SquareBracketStart,
344
-                    Token::SquareBracketEnd]);
345
-
346
-        assert_eq!(tokenize("5").unwrap(),
347
-                   [Token::Number("5")]);
348
-        assert_eq!(tokenize("-3").unwrap(),
349
-                   [Token::Number("-3")]);
350
-        assert_eq!(tokenize("3.14").unwrap(),
351
-                   [Token::Number("3.14")]);
352
-        assert_eq!(tokenize("-0.34").unwrap(),
353
-                   [Token::Number("-0.34")]);
354
-
355
-        assert_eq!(tokenize("1 foo 3").unwrap(),
356
-                   [Token::Number("1"),
357
-                    Token::Whitespace(" "),
358
-                    Token::Name("foo"),
359
-                    Token::Whitespace(" "),
360
-                    Token::Number("3")]);
361
-
362
-        assert_eq!(tokenize("\"hello/world.\"").unwrap(),
363
-                   [Token::DoubleQuote,
364
-                    Token::Name("hello/world."),
365
-                    Token::DoubleQuote]);
366
-
367
-        assert_eq!(tokenize("hello\nworld").unwrap(),
368
-                   [Token::Name("hello"),
369
-                    Token::Whitespace("\n"),
370
-                    Token::Name("world")]);
371
-
372
-        assert_eq!(tokenize("hello ; some comment").unwrap(),
373
-                   [Token::Name("hello"),
374
-                    Token::Whitespace(" "),
375
-                    Token::Comment(" some comment")]);
376
-
377
-        assert_eq!(tokenize("hello ; some comment\n(valid)").unwrap(),
378
-                   [Token::Name("hello"),
379
-                    Token::Whitespace(" "),
380
-                    Token::Comment(" some comment"),
381
-                    Token::Whitespace("\n"),
382
-                    Token::ParenStart,
383
-                    Token::Name("valid"),
384
-                    Token::ParenEnd]);
330
+        assert_eq!(
331
+            tokenize("()").unwrap(),
332
+            [Token::ParenStart, Token::ParenEnd]
333
+        );
334
+
335
+        assert_eq!(
336
+            tokenize("( )").unwrap(),
337
+            [Token::ParenStart, Token::Whitespace(" "), Token::ParenEnd]
338
+        );
339
+
340
+        assert_eq!(
341
+            tokenize("[]").unwrap(),
342
+            [Token::SquareBracketStart, Token::SquareBracketEnd]
343
+        );
344
+
345
+        assert_eq!(tokenize("5").unwrap(), [Token::Number("5")]);
346
+        assert_eq!(tokenize("-3").unwrap(), [Token::Number("-3")]);
347
+        assert_eq!(tokenize("3.14").unwrap(), [Token::Number("3.14")]);
348
+        assert_eq!(tokenize("-0.34").unwrap(), [Token::Number("-0.34")]);
349
+
350
+        assert_eq!(
351
+            tokenize("1 foo 3").unwrap(),
352
+            [
353
+                Token::Number("1"),
354
+                Token::Whitespace(" "),
355
+                Token::Name("foo"),
356
+                Token::Whitespace(" "),
357
+                Token::Number("3")
358
+            ]
359
+        );
360
+
361
+        assert_eq!(
362
+            tokenize("\"hello/world.\"").unwrap(),
363
+            [Token::String("hello/world.")]
364
+        );
365
+
366
+        assert_eq!(
367
+            tokenize("hello\nworld").unwrap(),
368
+            [
369
+                Token::Name("hello"),
370
+                Token::Whitespace("\n"),
371
+                Token::Name("world")
372
+            ]
373
+        );
374
+
375
+        assert_eq!(
376
+            tokenize("hello ; some comment").unwrap(),
377
+            [
378
+                Token::Name("hello"),
379
+                Token::Whitespace(" "),
380
+                Token::Comment(" some comment")
381
+            ]
382
+        );
383
+
384
+        assert_eq!(
385
+            tokenize("hello ; some comment\n(valid)").unwrap(),
386
+            [
387
+                Token::Name("hello"),
388
+                Token::Whitespace(" "),
389
+                Token::Comment(" some comment"),
390
+                Token::Whitespace("\n"),
391
+                Token::ParenStart,
392
+                Token::Name("valid"),
393
+                Token::ParenEnd
394
+            ]
395
+        );
385 396
     }
386 397
 
387 398
     fn ast(s: &str) -> Vec<Node> {
@@ -390,41 +401,60 @@ mod tests {
390 401
 
391 402
     #[test]
392 403
     fn test_parser() {
393
-        assert_eq!(ast("hello"),
394
-                   [Node::Name("hello".to_string())]);
395
-        assert_eq!(ast("hello world"),
396
-                   [Node::Name("hello".to_string()),
397
-                    Node::Name("world".to_string())]);
398
-        assert_eq!(ast("hello: world"),
399
-                   [Node::Label("hello".to_string()),
400
-                    Node::Name("world".to_string())]);
401
-        assert_eq!(ast("42 102"),
402
-                   [Node::Float(42.0),
403
-                    Node::Float(102.0)]);
404
-
405
-        assert_eq!(ast("\"path/to/texture.png\""),
406
-                   [Node::String("path/to/texture.png".to_string())]);
407
-
408
-        assert_eq!(ast("hello world ; some comment"),
409
-                   [Node::Name("hello".to_string()),
410
-                    Node::Name("world".to_string()),]);
411
-
412
-        assert_eq!(ast("(hello world)"),
413
-                   [Node::List(vec![Node::Name("hello".to_string()),
414
-                                    Node::Name("world".to_string())])]);
415
-
416
-        assert_eq!(ast("(hello world (1 2 3))"),
417
-                   [Node::List(vec![Node::Name("hello".to_string()),
418
-                                    Node::Name("world".to_string()),
419
-                                    Node::List(vec![Node::Float(1.0),
420
-                                                    Node::Float(2.0),
421
-                                                    Node::Float(3.0)])])]);
422
-
423
-        assert_eq!(ast("(hello world [1 2 3])"),
424
-                   [Node::List(vec![Node::Name("hello".to_string()),
425
-                                    Node::Name("world".to_string()),
426
-                                    Node::Vector(vec![Node::Float(1.0),
427
-                                                      Node::Float(2.0),
428
-                                                      Node::Float(3.0)])])]);
404
+        assert_eq!(ast("hello"), [Node::Name("hello".to_string())]);
405
+        assert_eq!(
406
+            ast("hello world"),
407
+            [
408
+                Node::Name("hello".to_string()),
409
+                Node::Name("world".to_string())
410
+            ]
411
+        );
412
+        assert_eq!(
413
+            ast("hello: world"),
414
+            [
415
+                Node::Label("hello".to_string()),
416
+                Node::Name("world".to_string())
417
+            ]
418
+        );
419
+        assert_eq!(ast("42 102"), [Node::Float(42.0), Node::Float(102.0)]);
420
+
421
+        assert_eq!(
422
+            ast("\"path/to/texture.png\""),
423
+            [Node::String("path/to/texture.png".to_string())]
424
+        );
425
+
426
+        assert_eq!(
427
+            ast("hello world ; some comment"),
428
+            [
429
+                Node::Name("hello".to_string()),
430
+                Node::Name("world".to_string()),
431
+            ]
432
+        );
433
+
434
+        assert_eq!(
435
+            ast("(hello world)"),
436
+            [Node::List(vec![
437
+                Node::Name("hello".to_string()),
438
+                Node::Name("world".to_string())
439
+            ])]
440
+        );
441
+
442
+        assert_eq!(
443
+            ast("(hello world (1 2 3))"),
444
+            [Node::List(vec![
445
+                Node::Name("hello".to_string()),
446
+                Node::Name("world".to_string()),
447
+                Node::List(vec![Node::Float(1.0), Node::Float(2.0), Node::Float(3.0)])
448
+            ])]
449
+        );
450
+
451
+        assert_eq!(
452
+            ast("(hello world [1 2 3])"),
453
+            [Node::List(vec![
454
+                Node::Name("hello".to_string()),
455
+                Node::Name("world".to_string()),
456
+                Node::Vector(vec![Node::Float(1.0), Node::Float(2.0), Node::Float(3.0)])
457
+            ])]
458
+        );
429 459
     }
430 460
 }

+ 2
- 1
src/picture.rs View File

@@ -1,6 +1,7 @@
1
-#[derive(Clone, Copy)]
1
+#[derive(Clone)]
2 2
 pub struct Picture {
3 3
     pub width: u32,
4 4
     pub height: u32,
5 5
     pub samples: u32,
6
+    pub output: String,
6 7
 }

+ 0
- 173
src/scene.rs View File

@@ -1,181 +1,8 @@
1 1
 use camera::*;
2
-use cgmath::prelude::*;
3
-use material::*;
4
-use picture::*;
5
-use rng::ThreadLocalRng;
6 2
 use shape::*;
7
-use types::*;
8
-
9
-// short-term hack
10
-pub enum ScenePreset {
11
-    Basic,
12
-    LoadOfBalls,
13
-}
14 3
 
15 4
 pub struct Scene {
16 5
     pub camera: Camera,
17 6
     pub hitables: HitableList,
18 7
     // lights etc
19 8
 }
20
-
21
-pub fn get_scene(scene_preset: ScenePreset, picture: Picture, rng: &mut ThreadLocalRng) -> Scene {
22
-    match scene_preset {
23
-        ScenePreset::Basic => basic(picture),
24
-        ScenePreset::LoadOfBalls => load_of_balls(picture, rng),
25
-    }
26
-}
27
-
28
-fn basic(picture: Picture) -> Scene {
29
-    let mut world = HitableList::new();
30
-
31
-    world.add_sphere(Sphere {
32
-        center: V3::new(0.0, 0.0, -1.0),
33
-        radius: 0.5,
34
-        material: Box::new(Lambertian {
35
-            albedo: V3::new(0.8, 0.3, 0.3),
36
-        }),
37
-    });
38
-
39
-    world.add_sphere(Sphere {
40
-        center: V3::new(0.0, -100.5, -1.0),
41
-        radius: 100.0,
42
-        material: Box::new(Lambertian {
43
-            albedo: V3::new(0.8, 0.8, 0.0),
44
-        }),
45
-    });
46
-
47
-    world.add_sphere(Sphere {
48
-        center: V3::new(1.0, 0.0, -1.0),
49
-        radius: 0.5,
50
-        material: Box::new(Metal {
51
-            albedo: V3::new(0.8, 0.6, 0.2),
52
-            fuzz: 1.0,
53
-        }),
54
-    });
55
-
56
-    world.add_sphere(Sphere {
57
-        center: V3::new(-1.0, 0.0, -1.0),
58
-        radius: 0.5,
59
-        material: Box::new(Metal {
60
-            albedo: V3::new(0.8, 0.8, 0.8),
61
-            fuzz: 0.3,
62
-        }),
63
-    });
64
-
65
-    let look_from = V3::new(0.0, 0.0, 0.0);
66
-    let look_at = V3::new(0.0, 0.0, -1.0);
67
-    let dist_to_focus = (look_from - look_at).magnitude();
68
-    let aperture = 0.0;
69
-
70
-    let camera = Camera::new(
71
-        look_from,
72
-        look_at,
73
-        V3::new(0.0, 1.0, 0.0),
74
-        90.0,
75
-        picture.width as S / picture.height as S,
76
-        aperture,
77
-        dist_to_focus,
78
-    );
79
-
80
-    Scene {
81
-        camera,
82
-        hitables: world,
83
-    }
84
-}
85
-
86
-fn load_of_balls(picture: Picture, rng: &mut ThreadLocalRng) -> Scene {
87
-    let mut world = HitableList::new();
88
-
89
-    world.add_sphere(Sphere {
90
-        center: V3::new(0.0, -1000.0, 0.0),
91
-        radius: 1000.0,
92
-        material: Box::new(Lambertian {
93
-            albedo: V3::new(0.5, 0.5, 0.5),
94
-        }),
95
-    });
96
-
97
-    for a in -11..11 {
98
-        for b in -11..11 {
99
-            let choose_mat = rng.gen();
100
-            let center = V3::new(a as S + 0.9 * rng.gen(), 0.2, b as S + 0.9 * rng.gen());
101
-
102
-            if (center - V3::new(4.0, 0.2, 0.0)).magnitude() > 0.9 {
103
-                if choose_mat < 0.8 {
104
-                    world.add_sphere(Sphere {
105
-                        center,
106
-                        radius: 0.2,
107
-                        material: Box::new(Lambertian {
108
-                            albedo: V3::new(
109
-                                rng.gen() * rng.gen(),
110
-                                rng.gen() * rng.gen(),
111
-                                rng.gen() * rng.gen(),
112
-                            ),
113
-                        }),
114
-                    });
115
-                } else if choose_mat < 0.95 {
116
-                    world.add_sphere(Sphere {
117
-                        center,
118
-                        radius: 0.2,
119
-                        material: Box::new(Metal {
120
-                            albedo: V3::new(
121
-                                0.5 * (1.0 + rng.gen()),
122
-                                0.5 * (1.0 + rng.gen()),
123
-                                0.5 * (1.0 + rng.gen()),
124
-                            ),
125
-                            fuzz: 0.5 * rng.gen(),
126
-                        }),
127
-                    });
128
-                } else {
129
-                    world.add_sphere(Sphere {
130
-                        center,
131
-                        radius: 0.2,
132
-                        material: Box::new(Dielectric { ref_idx: 1.5 }),
133
-                    });
134
-                }
135
-            }
136
-        }
137
-    }
138
-
139
-    world.add_sphere(Sphere {
140
-        center: V3::new(0.0, 1.0, 0.0),
141
-        radius: 1.0,
142
-        material: Box::new(Dielectric { ref_idx: 1.5 }),
143
-    });
144
-
145
-    world.add_sphere(Sphere {
146
-        center: V3::new(-4.0, 1.0, 0.0),
147
-        radius: 1.0,
148
-        material: Box::new(Lambertian {
149
-            albedo: V3::new(0.4, 0.2, 0.1),
150
-        }),
151
-    });
152
-
153
-    world.add_sphere(Sphere {
154
-        center: V3::new(4.0, 1.0, 0.0),
155
-        radius: 1.0,
156
-        material: Box::new(Metal {
157
-            albedo: V3::new(0.7, 0.6, 0.5),
158
-            fuzz: 0.0,
159
-        }),
160
-    });
161
-
162
-    let look_from = V3::new(13.0, 2.0, 3.0);
163
-    let look_at = V3::new(0.0, 0.0, 0.0);
164
-    let dist_to_focus = (look_from - look_at).magnitude();
165
-    let aperture = 0.05;
166
-
167
-    let camera = Camera::new(
168
-        look_from,
169
-        look_at,
170
-        V3::new(0.0, 1.0, 0.0),
171
-        18.0,
172
-        picture.width as S / picture.height as S,
173
-        aperture,
174
-        dist_to_focus,
175
-    );
176
-
177
-    Scene {
178
-        camera,
179
-        hitables: world,
180
-    }
181
-}

+ 342
- 0
src/scene_parser.rs View File

@@ -0,0 +1,342 @@
1
+use camera::*;
2
+use cgmath::prelude::*;
3
+use material::*;
4
+use picture::*;
5
+use shape::*;
6
+use types::*;
7
+
8
+use error::*;
9
+use scene::Scene;
10
+use parser::{parse, Node};
11
+
12
+use std::fs::File;
13
+use std::io::prelude::*;
14
+
15
+pub fn parse_scene(filename: &str) -> Result<(Scene, Picture)> {
16
+    let mut f = File::open(filename)?;
17
+    let mut contents = String::new();
18
+    f.read_to_string(&mut contents)?;
19
+
20
+    let ast = parse(&contents)?;
21
+    let (scene, picture) = create_scene(&ast)?;
22
+
23
+    Ok((scene, picture))
24
+}
25
+
26
+
27
+fn create_scene(ast: &Vec<Node>) -> Result<(Scene, Picture)> {
28
+    let mut world = HitableList::new();
29
+
30
+    let mut picture = Picture {
31
+        width: 100,
32
+        height: 50,
33
+        samples: 1,
34
+        output: "auto.png".to_string(),
35
+    };
36
+
37
+    let look_from = V3::new(0.0, 0.0, 0.0);
38
+    let look_at = V3::new(0.0, 0.0, -1.0);
39
+    let dist_to_focus = (look_from - look_at).magnitude();
40
+    let aperture = 0.0;
41
+
42
+    let mut camera = Camera::new(
43
+        look_from,
44
+        look_at,
45
+        V3::new(0.0, 1.0, 0.0),
46
+        90.0,
47
+        picture.width as S / picture.height as S,
48
+        aperture,
49
+        dist_to_focus,
50
+    );
51
+
52
+    // first search for a picture decl.
53
+    for n in ast {
54
+        if declaring(&n, "picture") {
55
+            picture = create_picture(&n)?;
56
+            break;
57
+        }
58
+    }
59
+    // then a camera
60
+    for n in ast {
61
+        if declaring(&n, "camera") {
62
+            camera = create_camera(&n, &picture)?;
63
+            break;
64
+        }
65
+    }
66
+    // now parse the geometry
67
+    for n in ast {
68
+        if declaring(&n, "sphere") {
69
+            let sphere = create_sphere(&n)?;
70
+            world.add_sphere(sphere);
71
+        }
72
+    }
73
+
74
+    Ok((Scene {
75
+        camera,
76
+        hitables: world,
77
+    }, picture))
78
+}
79
+
80
+fn create_picture(node: &Node) -> Result<Picture> {
81
+    match node {
82
+        Node::List(nodes) => {
83
+            let args = &nodes.as_slice()[1..];
84
+
85
+            let mut width = 0.0;
86
+            let mut height = 0.0;
87
+            let mut samples = 0.0;
88
+            let mut output = "".to_string();
89
+
90
+            let mut kv = args;
91
+
92
+            loop {
93
+                if kv.len() < 2 {
94
+                    return Ok(Picture {
95
+                        width: width as u32,
96
+                        height: height as u32,
97
+                        samples: samples as u32,
98
+                        output: output,
99
+                    })
100
+                } else {
101
+                    let k = &kv[0];
102
+                    let v = &kv[1];
103
+                    kv = &kv[2..];
104
+
105
+                    let name = get_label_name(k)?;
106
+                    match name.as_ref() {
107
+                        "width" => width = get_float(v)?,
108
+                        "height" => height = get_float(v)?,
109
+                        "samples" => samples = get_float(v)?,
110
+                        "output" => output = get_string(v)?,
111
+                        _ => return Err(RayTracerError::SceneParser)
112
+                    }
113
+                }
114
+            }
115
+        },
116
+        _ => Err(RayTracerError::SceneParser)
117
+    }
118
+}
119
+
120
+fn create_camera(node: &Node, picture: &Picture) -> Result<Camera> {
121
+    match node {
122
+        Node::List(nodes) => {
123
+            let args = &nodes.as_slice()[1..];
124
+
125
+            let mut look_from = V3::new(0.0, 0.0, 0.0);
126
+            let mut look_at = V3::new(0.0, 0.0, -1.0);
127
+            let mut look_up = V3::new(0.0, 1.0, 0.0);
128
+            let mut vfov = 90.0;
129
+            let mut aspect = picture.width as S / picture.height as S;
130
+            let mut aperture = 0.0;
131
+            let mut dist_to_focus = (look_from - look_at).magnitude();
132
+            let mut given_dist_to_focus = false;
133
+
134
+            let mut kv = args;
135
+
136
+            loop {
137
+                if kv.len() < 2 {
138
+                    if !given_dist_to_focus {
139
+                        dist_to_focus = (look_from - look_at).magnitude();
140
+                    }
141
+                    return Ok(Camera::new(
142
+                        look_from,
143
+                        look_at,
144
+                        look_up,
145
+                        vfov,
146
+                        aspect,
147
+                        aperture,
148
+                        dist_to_focus,
149
+                    ))
150
+                } else {
151
+                    let k = &kv[0];
152
+                    let v = &kv[1];
153
+                    kv = &kv[2..];
154
+
155
+                    let name = get_label_name(k)?;
156
+                    match name.as_ref() {
157
+                        "look-from" => look_from = get_v3(v)?,
158
+                        "look-at" => look_at = get_v3(v)?,
159
+                        "look-up" => look_up = get_v3(v)?,
160
+                        "vfov" => vfov = get_float(v)?,
161
+                        "aspect" => aspect = get_float(v)?,
162
+                        "aperture" => aperture = get_float(v)?,
163
+                        "focus-dist" => {
164
+                            dist_to_focus = get_float(v)?;
165
+                            given_dist_to_focus = true;
166
+                        },
167
+                        _ => return Err(RayTracerError::SceneParser)
168
+                    }
169
+                }
170
+            }
171
+        },
172
+        _ => Err(RayTracerError::SceneParser)
173
+    }
174
+}
175
+
176
+fn create_sphere(node: &Node) -> Result<Sphere> {
177
+    match node {
178
+        Node::List(nodes) => {
179
+            let args = &nodes.as_slice()[1..];
180
+
181
+            let mut center = V3::new(0.0, 0.0, 0.0);
182
+            let mut radius = 0.0;
183
+            let mut material: Box<Material> = Box::new(Lambertian {
184
+                albedo: V3::new(0.8, 0.8, 0.0),
185
+            });
186
+
187
+            let mut kv = args;
188
+
189
+            loop {
190
+                if kv.len() < 2 {
191
+                    return Ok(Sphere {
192
+                        center,
193
+                        radius,
194
+                        material: material
195
+                    })
196
+                } else {
197
+                    let k = &kv[0];
198
+                    let v = &kv[1];
199
+                    kv = &kv[2..];
200
+
201
+                    let name = get_label_name(k)?;
202
+                    match name.as_ref() {
203
+                        "center" => center = get_v3(v)?,
204
+                        "radius" => radius = get_float(v)?,
205
+                        "material" => material = get_material(v)?,
206
+                        _ => return Err(RayTracerError::SceneParser)
207
+                    }
208
+                }
209
+            }
210
+        },
211
+        _ => Err(RayTracerError::SceneParser)
212
+    }
213
+}
214
+
215
+fn get_material(node: &Node) -> Result<Box<Material>> {
216
+    if declaring(&node, "metal") {
217
+        return create_metal(node);
218
+    }
219
+    if declaring(&node, "lambertian") {
220
+        return create_lambertian(node);
221
+    }
222
+    Err(RayTracerError::SceneParser)
223
+}
224
+
225
+fn create_metal(node: &Node) -> Result<Box<Material>> {
226
+    match node {
227
+        Node::List(nodes) => {
228
+            let args = &nodes.as_slice()[1..];
229
+
230
+            let mut albedo = V3::new(0.0, 0.0, 0.0);
231
+            let mut fuzz = 0.0;
232
+
233
+            let mut kv = args;
234
+
235
+            loop {
236
+                if kv.len() < 2 {
237
+                    return Ok(Box::new(Metal {
238
+                        albedo,
239
+                        fuzz,
240
+                    }))
241
+                } else {
242
+                    let k = &kv[0];
243
+                    let v = &kv[1];
244
+                    kv = &kv[2..];
245
+
246
+                    let name = get_label_name(k)?;
247
+                    match name.as_ref() {
248
+                        "albedo" => albedo = get_v3(v)?,
249
+                        "fuzz" => fuzz = get_float(v)?,
250
+                        _ => return Err(RayTracerError::SceneParser)
251
+                    }
252
+                }
253
+            }
254
+        },
255
+        _ => Err(RayTracerError::SceneParser)
256
+    }
257
+}
258
+
259
+fn create_lambertian(node: &Node) -> Result<Box<Material>> {
260
+    match node {
261
+        Node::List(nodes) => {
262
+            let args = &nodes.as_slice()[1..];
263
+
264
+            let mut albedo = V3::new(0.0, 0.0, 0.0);
265
+
266
+            let mut kv = args;
267
+
268
+            loop {
269
+                if kv.len() < 2 {
270
+                    return Ok(Box::new(Lambertian {
271
+                        albedo,
272
+                    }))
273
+                } else {
274
+                    let k = &kv[0];
275
+                    let v = &kv[1];
276
+                    kv = &kv[2..];
277
+
278
+                    let name = get_label_name(k)?;
279
+                    match name.as_ref() {
280
+                        "albedo" => albedo = get_v3(v)?,
281
+                        _ => return Err(RayTracerError::SceneParser)
282
+                    }
283
+                }
284
+            }
285
+        },
286
+        _ => Err(RayTracerError::SceneParser)
287
+    }
288
+}
289
+
290
+
291
+fn get_v3(node: &Node) -> Result<V3> {
292
+    match node {
293
+        Node::Vector(nodes) => {
294
+            if nodes.len() == 3 {
295
+                Ok(V3::new(get_float(&nodes[0])?,
296
+                           get_float(&nodes[1])?,
297
+                           get_float(&nodes[2])?))
298
+            } else {
299
+                Err(RayTracerError::SceneParser)
300
+            }
301
+        },
302
+        _ => Err(RayTracerError::SceneParser),
303
+    }
304
+}
305
+
306
+fn get_string(node: &Node) -> Result<String> {
307
+    match node {
308
+        Node::String(txt) => Ok(txt.to_string()),
309
+        _ => Err(RayTracerError::SceneParser),
310
+    }
311
+}
312
+
313
+fn get_float(node: &Node) -> Result<S> {
314
+    match node {
315
+        Node::Float(f) => Ok(*f),
316
+        _ => Err(RayTracerError::SceneParser),
317
+    }
318
+}
319
+
320
+fn get_label_name(node: &Node) -> Result<String> {
321
+    match node {
322
+        Node::Label(txt) => Ok(txt.to_string()),
323
+        _ => Err(RayTracerError::SceneParser)
324
+    }
325
+}
326
+
327
+// is the node a list and is it a declaration of the given name?
328
+fn declaring(node: &Node, name: &str) -> bool {
329
+    match node {
330
+        Node::List(nodes) => {
331
+            if nodes.is_empty() {
332
+                false
333
+            } else {
334
+                match nodes[0] {
335
+                    Node::Name(ref node_name) => node_name == name,
336
+                    _ => false,
337
+                }
338
+            }
339
+        },
340
+        _ => false
341
+    }
342
+}