Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8a37c6e9a | ||
|
|
fa77c0af29 | ||
|
|
6f8e3714da | ||
|
|
de2c51376a | ||
|
|
0ba0ebfe71 | ||
|
|
a3684b20b6 | ||
|
|
c1c8c46ac7 | ||
|
|
0a979c487c | ||
|
|
88bfc29323 | ||
|
|
e964908e46 | ||
|
|
bf09c094e1 | ||
|
|
1475434c4c | ||
|
|
e13608b287 | ||
|
|
c8f6d36bdc | ||
|
|
6b77b1a666 | ||
|
|
d7394027b1 | ||
|
|
68d5b39cfd | ||
|
|
0d36f13b17 | ||
|
|
88d482dcb3 | ||
|
|
d71be68fae | ||
|
|
1947ea553c | ||
|
|
7eaf5f9555 | ||
|
|
d761433382 | ||
|
|
e3d4c88f66 | ||
|
|
94105a883f | ||
|
|
c05f2b1b59 | ||
|
|
cc041168a1 |
No files matched your search
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
/fart
|
|
||||||
*.bmp
|
*.bmp
|
||||||
*.png
|
*.png
|
||||||
|
/.propane*
|
||||||
/.rscons*
|
/.rscons*
|
||||||
/build/
|
/build/
|
||||||
|
/fart
|
||||||
+19
-4
@@ -3,10 +3,25 @@ configure do
|
|||||||
check_cfg package: "libpng"
|
check_cfg package: "libpng"
|
||||||
end
|
end
|
||||||
|
|
||||||
env do |env|
|
class Propane < Builder
|
||||||
env["CCFLAGS"] += %w[-Wall -O2 -std=c++23]
|
def run(options)
|
||||||
env["CPPPATH"] += %w[src]
|
@sources += ["propane"] unless @sources.include?("propane")
|
||||||
|
if @command
|
||||||
|
finalize_command
|
||||||
|
else
|
||||||
|
command = %W[./propane -w --log #{@target}.log #{@sources.first} #{@target}]
|
||||||
|
standard_command("Propane <target>#{@target}<reset>", command, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
sources = glob("src/**/*.cpp")
|
env do |env|
|
||||||
|
env.add_builder(Propane)
|
||||||
|
env["CCFLAGS"] += %w[-Wall -O2 -std=c++23 -include fart/common.h]
|
||||||
|
env["CPPPATH"] += %W[src #{env.build_root}/src]
|
||||||
|
|
||||||
|
env.Propane("^/src/fart/propane.cpp", "src/fart.propane")
|
||||||
|
env.barrier
|
||||||
|
sources = glob("src/**/*.cpp") + ["#{env.build_root}/src/fart/propane.cpp"]
|
||||||
env.Program("fart", sources)
|
env.Program("fart", sources)
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2010-2026 Josh Holtrop
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
script = File.join(File.dirname(__FILE__), ".propane-5.2.0-58c963344c9bc893689cbae5e4c49b1b.rb")
|
||||||
|
unless File.exist?(script)
|
||||||
|
if File.read(__FILE__, mode: "rb") =~ /^#==>(.*)/
|
||||||
|
require "zlib"
|
||||||
|
require "base64"
|
||||||
|
encoded_compressed = $1
|
||||||
|
unescaped_compressed = Base64.decode64(encoded_compressed)
|
||||||
|
inflated = Zlib::Inflate.inflate(unescaped_compressed)
|
||||||
|
File.open(script, "wb") do |fh|
|
||||||
|
fh.write(inflated)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise "Could not decompress."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
load script
|
||||||
|
if __FILE__ == $0
|
||||||
|
Propane::CLI.new.run(ARGV)
|
||||||
|
end
|
||||||
|
#==>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 truncated
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
tree;
|
||||||
|
tree_prefix P;
|
||||||
|
ptype double;
|
||||||
|
|
||||||
|
<<header
|
||||||
|
/* User terminate codes. */
|
||||||
|
#define PARSER_TERMINATE_NUMBER_RANGE 1u
|
||||||
|
>>
|
||||||
|
|
||||||
|
<<
|
||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
/* Store the numeric value of the matched text in *out. Returns false if the
|
||||||
|
* value cannot be represented. */
|
||||||
|
static bool parse_number(uint8_t const * match_text, size_t match_length, double * out)
|
||||||
|
{
|
||||||
|
const char * first = (const char *)match_text;
|
||||||
|
const char * last = first + match_length;
|
||||||
|
std::from_chars_result r = std::from_chars(first, last, *out);
|
||||||
|
return (r.ec == std::errc()) && (r.ptr == last);
|
||||||
|
}
|
||||||
|
>>
|
||||||
|
|
||||||
|
drop /\s+/;
|
||||||
|
drop /#.*/;
|
||||||
|
|
||||||
|
token background;
|
||||||
|
token box;
|
||||||
|
token camera;
|
||||||
|
token color;
|
||||||
|
token comma /,/;
|
||||||
|
token cyl;
|
||||||
|
token gt />/;
|
||||||
|
token height;
|
||||||
|
token intersect;
|
||||||
|
token lcurly /\{/;
|
||||||
|
token lt /</;
|
||||||
|
token material;
|
||||||
|
token options;
|
||||||
|
token plane;
|
||||||
|
token position;
|
||||||
|
token rcurly /\}/;
|
||||||
|
token rotate;
|
||||||
|
token scale;
|
||||||
|
token size;
|
||||||
|
token sphere;
|
||||||
|
token subtract;
|
||||||
|
token target;
|
||||||
|
token translate;
|
||||||
|
token union;
|
||||||
|
token up;
|
||||||
|
token width;
|
||||||
|
|
||||||
|
token uint /\d+/ <<
|
||||||
|
if (!parse_number(match_text, match_length, &$$))
|
||||||
|
{
|
||||||
|
$terminate(PARSER_TERMINATE_NUMBER_RANGE);
|
||||||
|
}
|
||||||
|
>>
|
||||||
|
token number /-?\d+(\.\d+)?/ <<
|
||||||
|
if (!parse_number(match_text, match_length, &$$))
|
||||||
|
{
|
||||||
|
$terminate(PARSER_TERMINATE_NUMBER_RANGE);
|
||||||
|
}
|
||||||
|
>>
|
||||||
|
|
||||||
|
start Scene;
|
||||||
|
|
||||||
|
Background -> background Vector;
|
||||||
|
|
||||||
|
Color -> color Vector;
|
||||||
|
|
||||||
|
Height -> height uint;
|
||||||
|
|
||||||
|
Number -> uint;
|
||||||
|
Number -> number;
|
||||||
|
|
||||||
|
Options -> options lcurly OptionsItems rcurly;
|
||||||
|
|
||||||
|
OptionsItems -> ;
|
||||||
|
OptionsItems -> OptionsItem OptionsItems;
|
||||||
|
|
||||||
|
OptionsItem -> Background;
|
||||||
|
OptionsItem -> Height;
|
||||||
|
OptionsItem -> Width;
|
||||||
|
|
||||||
|
Position -> position Vector;
|
||||||
|
|
||||||
|
Rotate -> rotate Number comma Vector;
|
||||||
|
Rotate -> rotate Number comma Vector lcurly SceneItems rcurly;
|
||||||
|
|
||||||
|
Scale -> scale Number;
|
||||||
|
Scale -> scale Number lcurly SceneItems rcurly;
|
||||||
|
Scale -> scale Vector;
|
||||||
|
Scale -> scale Vector lcurly SceneItems rcurly;
|
||||||
|
|
||||||
|
SceneItemBlock -> SceneItemName lcurly SceneItems rcurly;
|
||||||
|
|
||||||
|
Scene -> Options? SceneItems;
|
||||||
|
|
||||||
|
SceneItems -> ;
|
||||||
|
SceneItems -> SceneItem SceneItems;
|
||||||
|
|
||||||
|
SceneItem -> Color;
|
||||||
|
SceneItem -> Position;
|
||||||
|
SceneItem -> Rotate;
|
||||||
|
SceneItem -> Scale;
|
||||||
|
SceneItem -> SceneItemBlock;
|
||||||
|
SceneItem -> Size;
|
||||||
|
SceneItem -> Target;
|
||||||
|
SceneItem -> Translate;
|
||||||
|
SceneItem -> Up;
|
||||||
|
|
||||||
|
SceneItemName -> box;
|
||||||
|
SceneItemName -> camera;
|
||||||
|
SceneItemName -> cyl;
|
||||||
|
SceneItemName -> material;
|
||||||
|
SceneItemName -> plane;
|
||||||
|
SceneItemName -> sphere;
|
||||||
|
|
||||||
|
Size -> size Number;
|
||||||
|
Size -> size Vector;
|
||||||
|
|
||||||
|
Target -> target Vector;
|
||||||
|
|
||||||
|
Translate -> translate Vector;
|
||||||
|
Translate -> translate Vector lcurly SceneItems rcurly;
|
||||||
|
|
||||||
|
Up -> up Vector;
|
||||||
|
|
||||||
|
Vector -> lt Number comma Number comma Number gt;
|
||||||
|
Vector -> lt Number comma Number comma Number comma Number gt;
|
||||||
|
|
||||||
|
Width -> width uint;
|
||||||
+222
-1
@@ -1,5 +1,226 @@
|
|||||||
#include <fart/Cli.h>
|
#include <fart/Cli.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
Cli::Cli()
|
#define FLAGS \
|
||||||
|
FLAG(height, "h", true) \
|
||||||
|
FLAG(height_l, "height", true) \
|
||||||
|
FLAG(help, "help", false) \
|
||||||
|
FLAG(output, "o", true) \
|
||||||
|
FLAG(output_l, "output", true) \
|
||||||
|
FLAG(threads, "t", true) \
|
||||||
|
FLAG(threads_l, "threads", true) \
|
||||||
|
FLAG(version, "version", false) \
|
||||||
|
FLAG(width, "w", true) \
|
||||||
|
FLAG(width_l, "width", true) \
|
||||||
|
|
||||||
|
#define FLAG(name, flag, arg) flag_##name,
|
||||||
|
typedef enum
|
||||||
{
|
{
|
||||||
|
FLAGS
|
||||||
|
flag__count
|
||||||
|
} flag_id_t;
|
||||||
|
#undef FLAG
|
||||||
|
|
||||||
|
#define FLAG(name, flag, arg) static const char flag_##name##_str[] = flag;
|
||||||
|
FLAGS
|
||||||
|
#undef FLAG
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
const char * flag;
|
||||||
|
size_t flag_length;
|
||||||
|
bool arg;
|
||||||
|
} flag_def_t;
|
||||||
|
|
||||||
|
#define FLAG(name, flag, arg) { \
|
||||||
|
flag_##name##_str, \
|
||||||
|
sizeof(flag_##name##_str) - 1u, \
|
||||||
|
arg},
|
||||||
|
static const flag_def_t flag_defs[] = {
|
||||||
|
FLAGS
|
||||||
|
};
|
||||||
|
#undef FLAG
|
||||||
|
|
||||||
|
int Cli::run(int argc, char * argv[])
|
||||||
|
{
|
||||||
|
for (size_t i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
size_t dashes = 0u;
|
||||||
|
if (strncmp(argv[i], "--", 2) == 0)
|
||||||
|
{
|
||||||
|
dashes = 2u;
|
||||||
|
}
|
||||||
|
else if (strncmp(argv[i], "-", 1) == 0)
|
||||||
|
{
|
||||||
|
dashes = 1u;
|
||||||
|
}
|
||||||
|
if (dashes > 0u)
|
||||||
|
{
|
||||||
|
const char * flag = &argv[i][dashes];
|
||||||
|
size_t flag_idx;
|
||||||
|
for (flag_idx = 0u; flag_idx < flag__count; flag_idx++)
|
||||||
|
{
|
||||||
|
size_t flag_length = flag_defs[flag_idx].flag_length;
|
||||||
|
size_t flag_dashes = flag_length == 1u ? 1u : 2u;
|
||||||
|
if (flag_dashes != dashes)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (strncmp(flag, flag_defs[flag_idx].flag, flag_length) != 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (dashes == 2 && flag[flag_length] != '\0' && flag[flag_length] != '=')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool arg = flag_defs[flag_idx].arg;
|
||||||
|
const char * arg_value = nullptr;
|
||||||
|
if (arg)
|
||||||
|
{
|
||||||
|
if (flag[flag_length] == '\0')
|
||||||
|
{
|
||||||
|
if (i >= (argc - 1))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error: option %s requires an argument\n", flag_defs[flag_idx].flag);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
arg_value = argv[i];
|
||||||
|
}
|
||||||
|
else if (dashes == 1u)
|
||||||
|
{
|
||||||
|
arg_value = &flag[flag_length];
|
||||||
|
}
|
||||||
|
else if (flag[flag_length + 1] != '\0')
|
||||||
|
{
|
||||||
|
arg_value = &flag[flag_length + 1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error: option %s requires an argument\n", flag_defs[flag_idx].flag);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dashes == 2u && flag[flag_length] == '=')
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error: option %s does not take an argument\n", flag_defs[flag_idx].flag);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* TODO: support combining short flags, e.g. -cofoo */
|
||||||
|
}
|
||||||
|
int r = handle_option(flag_idx, arg_value);
|
||||||
|
if (r != 0)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (flag_idx == flag__count)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Unknown flag: %s\n", flag);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!this->input_file.empty())
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Extraneous argument: %s\n", argv[i]);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
this->input_file = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_size(const char * flag, const char * value, opt<size_t> * out)
|
||||||
|
{
|
||||||
|
bool valid = value[0] != '\0';
|
||||||
|
for (const char * c = value; *c != '\0'; c++)
|
||||||
|
{
|
||||||
|
if (*c < '0' || *c > '9')
|
||||||
|
{
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
errno = 0;
|
||||||
|
unsigned long long v = strtoull(value, nullptr, 10);
|
||||||
|
if (errno == 0 && v <= SIZE_MAX)
|
||||||
|
{
|
||||||
|
*out = (size_t)v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Error: invalid value for option %s: '%s'\n", flag, value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Cli::handle_option(size_t flag_idx, const char * arg_value)
|
||||||
|
{
|
||||||
|
switch (flag_idx)
|
||||||
|
{
|
||||||
|
case flag_height:
|
||||||
|
case flag_height_l:
|
||||||
|
if (!parse_size(flag_defs[flag_idx].flag, arg_value, &this->options.height))
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case flag_help:
|
||||||
|
printf("Usage: fart [options] input-file\n");
|
||||||
|
printf("\n");
|
||||||
|
printf(" Options:\n");
|
||||||
|
printf("\n");
|
||||||
|
printf(" -h, --height H Set render height to H\n");
|
||||||
|
printf(" --help Show this help and exit\n");
|
||||||
|
printf(" -o, --output F Write rendered image to file F\n");
|
||||||
|
printf(" -t, --threads N Use N render threads (default: number of CPUs)\n");
|
||||||
|
printf(" -w, --width W Set render width to W\n");
|
||||||
|
printf(" --version Show fart version and exit\n");
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case flag_output:
|
||||||
|
case flag_output_l:
|
||||||
|
this->output_file = arg_value;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case flag_threads:
|
||||||
|
case flag_threads_l:
|
||||||
|
if (!parse_size(flag_defs[flag_idx].flag, arg_value, &this->options.threads))
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (*this->options.threads == 0u)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error: option %s must be at least 1\n", flag_defs[flag_idx].flag);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case flag_width:
|
||||||
|
case flag_width_l:
|
||||||
|
if (!parse_size(flag_defs[flag_idx].flag, arg_value, &this->options.width))
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case flag_version:
|
||||||
|
printf("fart, version 0.0.0.0.0.1\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Internal error: unhandled flag_idx=%zu\n", flag_idx);
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
+10
-1
@@ -1,7 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <fart/Options.h>
|
||||||
|
|
||||||
class Cli
|
class Cli
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Cli();
|
Options options;
|
||||||
|
string_view input_file;
|
||||||
|
opt<string_view> output_file;
|
||||||
|
|
||||||
|
int run(int argc, char * argv[]);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int handle_option(size_t option_idx, const char * arg_value);
|
||||||
};
|
};
|
||||||
+80
-1
@@ -1,5 +1,84 @@
|
|||||||
#include <fart/Engine.h>
|
#include <fart/Engine.h>
|
||||||
|
#include <fart/Parser.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
Engine::Engine()
|
Engine::Engine(const Options & options)
|
||||||
|
: options(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Engine::~Engine()
|
||||||
|
{
|
||||||
|
delete scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Engine::run(string_view input_file, opt<string_view> output_file)
|
||||||
|
{
|
||||||
|
string output_path;
|
||||||
|
if (output_file)
|
||||||
|
{
|
||||||
|
output_path = *output_file;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string_view base = input_file;
|
||||||
|
if (base.ends_with(".fart"))
|
||||||
|
{
|
||||||
|
base.remove_suffix(5u);
|
||||||
|
}
|
||||||
|
output_path = string(base) + ".png";
|
||||||
|
}
|
||||||
|
|
||||||
|
image_writer.reset(ImageWriter::build(output_path));
|
||||||
|
if (!image_writer)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verify the output file can be written before rendering. */
|
||||||
|
FILE * fh = fopen(output_path.c_str(), "wb");
|
||||||
|
if (fh == nullptr)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error opening output file '%s': %s\n", output_path.c_str(), strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (fclose(fh) != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error closing output file '%s': %s\n", output_path.c_str(), strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (remove(output_path.c_str()) != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error removing output file '%s': %s\n", output_path.c_str(), strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete scene;
|
||||||
|
scene = Parser::load_scene(input_file);
|
||||||
|
if (!scene)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CLI options take precedence over scene options; defaults fill the rest. */
|
||||||
|
options.merge(scene->options);
|
||||||
|
options.apply_defaults();
|
||||||
|
|
||||||
|
Image image(*options.width, *options.height);
|
||||||
|
for (size_t y = 0u; y < *options.height; y++)
|
||||||
|
{
|
||||||
|
for (size_t x = 0u; x < *options.width; x++)
|
||||||
|
{
|
||||||
|
image[x, y] = *options.background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image_writer->write(image))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+13
-1
@@ -1,7 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <fart/ImageWriter.h>
|
||||||
|
#include <fart/Options.h>
|
||||||
|
#include <fart/Scene.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class Engine
|
class Engine
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Engine();
|
Engine(const Options & options);
|
||||||
|
~Engine();
|
||||||
|
int run(string_view input_file, opt<string_view> output_file);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Options options;
|
||||||
|
std::unique_ptr<ImageWriter> image_writer;
|
||||||
|
Scene * scene = nullptr;
|
||||||
};
|
};
|
||||||
+1
-2
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
#include <fart/Color.h>
|
#include <fart/Color.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct Image
|
struct Image
|
||||||
{
|
{
|
||||||
size_t width;
|
size_t width;
|
||||||
size_t height;
|
size_t height;
|
||||||
std::vector<Color> pixels;
|
vector<Color> pixels;
|
||||||
Image(size_t width, size_t height);
|
Image(size_t width, size_t height);
|
||||||
|
|
||||||
Color & operator[](size_t x, size_t y)
|
Color & operator[](size_t x, size_t y)
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
ImageWriter * ImageWriter::build(const std::string & fname)
|
ImageWriter * ImageWriter::build(const string & fname)
|
||||||
{
|
{
|
||||||
std::string ext = std::filesystem::path(fname).extension().string();
|
string ext = std::filesystem::path(fname).extension().string();
|
||||||
if (ext == ".png")
|
if (ext == ".png")
|
||||||
{
|
{
|
||||||
return new PngImageWriter(fname);
|
return new PngImageWriter(fname);
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
class ImageWriter
|
class ImageWriter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static ImageWriter * build(const std::string & fname);
|
static ImageWriter * build(const string & fname);
|
||||||
virtual ~ImageWriter() = default;
|
virtual ~ImageWriter() = default;
|
||||||
virtual bool write(const Image & image) = 0;
|
virtual bool write(const Image & image) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ImageWriter(const std::string & fname) : fname(fname) {}
|
ImageWriter(const string & fname) : fname(fname) {}
|
||||||
std::string fname;
|
string fname;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include <fart/Options.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
void Options::merge(const Options & other)
|
||||||
|
{
|
||||||
|
if (!width)
|
||||||
|
{
|
||||||
|
width = other.width;
|
||||||
|
}
|
||||||
|
if (!height)
|
||||||
|
{
|
||||||
|
height = other.height;
|
||||||
|
}
|
||||||
|
if (!threads)
|
||||||
|
{
|
||||||
|
threads = other.threads;
|
||||||
|
}
|
||||||
|
if (!background)
|
||||||
|
{
|
||||||
|
background = other.background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Options::apply_defaults()
|
||||||
|
{
|
||||||
|
if (!width)
|
||||||
|
{
|
||||||
|
width = 800u;
|
||||||
|
}
|
||||||
|
if (!height)
|
||||||
|
{
|
||||||
|
height = 600u;
|
||||||
|
}
|
||||||
|
if (!threads)
|
||||||
|
{
|
||||||
|
/* hardware_concurrency() may return 0 if it cannot be determined. */
|
||||||
|
size_t n = std::thread::hardware_concurrency();
|
||||||
|
threads = n > 0u ? n : 1u;
|
||||||
|
}
|
||||||
|
if (!background)
|
||||||
|
{
|
||||||
|
background = Color{0.0, 0.0, 0.0, 1.0};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fart/Color.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct Options
|
||||||
|
{
|
||||||
|
opt<size_t> width;
|
||||||
|
opt<size_t> height;
|
||||||
|
opt<size_t> threads;
|
||||||
|
opt<Color> background;
|
||||||
|
|
||||||
|
/* Set each field that is not already set to the value from other. */
|
||||||
|
void merge(const Options & other);
|
||||||
|
|
||||||
|
/* Fill in any unset fields with default values. */
|
||||||
|
void apply_defaults();
|
||||||
|
};
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
#include <fart/Parser.h>
|
||||||
|
#include <fart/propane.h>
|
||||||
|
#include <fart/read_file.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
Scene * Parser::load_scene(const string_view & input_file)
|
||||||
|
{
|
||||||
|
opt<vector<uint8_t>> input = read_file(input_file);
|
||||||
|
if (!input)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_context_t * context = p_context_new(input->data(), input->size());
|
||||||
|
size_t result = p_parse(context);
|
||||||
|
p_position_t pos = p_position(context);
|
||||||
|
int fname_len = (int)input_file.size();
|
||||||
|
const char * fname = input_file.data();
|
||||||
|
bool success = false;
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case P_SUCCESS:
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_DECODE_ERROR:
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: error: invalid UTF-8 encoding\n",
|
||||||
|
fname_len, fname, pos.row, pos.col);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_UNEXPECTED_INPUT:
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: error: unexpected input\n",
|
||||||
|
fname_len, fname, pos.row, pos.col);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_UNEXPECTED_TOKEN:
|
||||||
|
{
|
||||||
|
p_token_t token = p_token(context);
|
||||||
|
if (token == TOKEN___EOF)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: error: unexpected end of input\n",
|
||||||
|
fname_len, fname, pos.row, pos.col);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: error: unexpected token '%s'\n",
|
||||||
|
fname_len, fname, pos.row, pos.col, p_token_names[token]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case P_USER_TERMINATED:
|
||||||
|
if (p_user_terminate_code(context) == PARSER_TERMINATE_NUMBER_RANGE)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: error: number out of range\n",
|
||||||
|
fname_len, fname, pos.row, pos.col);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: internal error: unknown terminate code %zu\n",
|
||||||
|
fname_len, fname, pos.row, pos.col, p_user_terminate_code(context));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: internal error: unknown parser result %zu\n",
|
||||||
|
fname_len, fname, pos.row, pos.col, result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
p_context_delete(context);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scene * scene = build_scene(input_file, p_result(context));
|
||||||
|
p_context_delete(context);
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert a uint token value to a size_t, reporting an error if it does not
|
||||||
|
* fit. */
|
||||||
|
static bool token_to_size(const string_view & input_file, PToken token, opt<size_t> * out)
|
||||||
|
{
|
||||||
|
/* SIZE_MAX + 1, computed so that it is exactly representable as a double. */
|
||||||
|
const double size_limit = (double)(SIZE_MAX / 2u + 1u) * 2.0;
|
||||||
|
double value = token.pvalue();
|
||||||
|
if (value >= size_limit)
|
||||||
|
{
|
||||||
|
p_position_t pos = token.position();
|
||||||
|
fprintf(stderr, "%.*s:%u:%u: error: value out of range\n",
|
||||||
|
(int)input_file.size(), input_file.data(), pos.row, pos.col);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = (size_t)value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Color vector_to_color(PVector vector)
|
||||||
|
{
|
||||||
|
Color color{
|
||||||
|
vector.pNumber2().pToken().pvalue(),
|
||||||
|
vector.pNumber4().pToken().pvalue(),
|
||||||
|
vector.pNumber6().pToken().pvalue(),
|
||||||
|
};
|
||||||
|
if (PNumber a = vector.pNumber8(); p_node_valid(a))
|
||||||
|
{
|
||||||
|
color.a = a.pToken().pvalue();
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scene * Parser::build_scene(const string_view & input_file, PScene pscene)
|
||||||
|
{
|
||||||
|
Scene * scene = new Scene();
|
||||||
|
|
||||||
|
/* If the options block is absent, pOptions() returns a null node and so
|
||||||
|
* does pOptionsItems() on it, so the loop body is skipped. */
|
||||||
|
for (POptionsItems items = pscene.pOptions().pOptionsItems();
|
||||||
|
p_node_valid(items);
|
||||||
|
items = items.pOptionsItems())
|
||||||
|
{
|
||||||
|
POptionsItem item = items.pOptionsItem();
|
||||||
|
bool ok = true;
|
||||||
|
if (PBackground background = item.pBackground(); p_node_valid(background))
|
||||||
|
{
|
||||||
|
scene->options.background = vector_to_color(background.pVector());
|
||||||
|
}
|
||||||
|
else if (PHeight height = item.pHeight(); p_node_valid(height))
|
||||||
|
{
|
||||||
|
ok = token_to_size(input_file, height.pToken2(), &scene->options.height);
|
||||||
|
}
|
||||||
|
else if (PWidth width = item.pWidth(); p_node_valid(width))
|
||||||
|
{
|
||||||
|
ok = token_to_size(input_file, width.pToken2(), &scene->options.width);
|
||||||
|
}
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
delete scene;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fart/Scene.h>
|
||||||
|
|
||||||
|
struct PScene;
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Scene * load_scene(const string_view & input_file);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Scene * build_scene(const string_view & input_file, PScene pscene);
|
||||||
|
};
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
static uint8_t to_byte(double v)
|
static uint8_t to_byte(double v)
|
||||||
{
|
{
|
||||||
@@ -13,7 +12,7 @@ static uint8_t to_byte(double v)
|
|||||||
|
|
||||||
bool PngImageWriter::write(const Image & image)
|
bool PngImageWriter::write(const Image & image)
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> buffer(image.width * image.height * 4u);
|
vector<uint8_t> buffer(image.width * image.height * 4u);
|
||||||
for (size_t i = 0u; i < image.pixels.size(); i++)
|
for (size_t i = 0u; i < image.pixels.size(); i++)
|
||||||
{
|
{
|
||||||
const Color & c = image.pixels[i];
|
const Color & c = image.pixels[i];
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
class PngImageWriter : public ImageWriter
|
class PngImageWriter : public ImageWriter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PngImageWriter(const std::string & fname) : ImageWriter(fname) {}
|
PngImageWriter(const string & fname) : ImageWriter(fname) {}
|
||||||
bool write(const Image & image) override;
|
bool write(const Image & image) override;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fart/Options.h>
|
||||||
|
|
||||||
|
class Scene
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Options options;
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using string = std::string;
|
||||||
|
using string_view = std::string_view;
|
||||||
|
template <typename T> using opt = std::optional<T>;
|
||||||
|
template <typename T> using vector = std::vector<T>;
|
||||||
+12
-3
@@ -5,7 +5,16 @@
|
|||||||
int main(int argc, char * argv[])
|
int main(int argc, char * argv[])
|
||||||
{
|
{
|
||||||
Cli cli;
|
Cli cli;
|
||||||
Engine engine;
|
int rc = cli.run(argc, argv);
|
||||||
printf("Hello\n");
|
if (rc > 0)
|
||||||
return 0;
|
{
|
||||||
|
return rc - 1;
|
||||||
|
}
|
||||||
|
if (cli.input_file.empty())
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error: no input file specified\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Engine engine(cli.options);
|
||||||
|
return engine.run(cli.input_file, cli.output_file);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include <fart/read_file.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
opt<vector<uint8_t>> read_file(string_view fname)
|
||||||
|
{
|
||||||
|
string path(fname);
|
||||||
|
FILE * fh = fopen(path.c_str(), "rb");
|
||||||
|
if (fh == nullptr)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error opening input file '%s': %s\n", path.c_str(), strerror(errno));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<uint8_t> buffer;
|
||||||
|
uint8_t chunk[4096];
|
||||||
|
size_t n;
|
||||||
|
while ((n = fread(chunk, 1u, sizeof(chunk), fh)) > 0u)
|
||||||
|
{
|
||||||
|
buffer.insert(buffer.end(), chunk, chunk + n);
|
||||||
|
}
|
||||||
|
if (ferror(fh))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error reading input file '%s': %s\n", path.c_str(), strerror(errno));
|
||||||
|
fclose(fh);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
fclose(fh);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
opt<vector<uint8_t>> read_file(string_view fname);
|
||||||
Reference in new issue
Block a user