1 // Copyright 2016-2021 Doug Moen
2 // Licensed under the Apache License, version 2.0
3 // See accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0
4 
5 #include <libcurv/shape.h>
6 
7 #include <libcurv/context.h>
8 #include <libcurv/exception.h>
9 #include <libcurv/frame.h>
10 #include <libcurv/function.h>
11 #include <libcurv/program.h>
12 #include <libcurv/render.h>
13 #include <libcurv/sc_context.h>
14 
15 #include <cmath>
16 
17 namespace curv {
18 
Shape_Program(Program & prog)19 Shape_Program::Shape_Program(
20     Program& prog)
21 :
22     sstate_(prog.sstate_),
23     nub_(nub_phrase(prog.phrase_))
24 {
25     // mark initial state (no shape has been recognized yet)
26     is_2d_ = false;
27     is_3d_ = false;
28 }
29 
30 BBox
from_value(Value val,const Context & cx)31 BBox::from_value(Value val, const Context& cx)
32 {
33     auto list = val.to<List>(cx);
34     list->assert_size(2, cx);
35 
36     At_Index mincx(0, cx);
37     auto mins = list->at(0).to<List>(mincx);
38     mins->assert_size(3, mincx);
39 
40     At_Index maxcx(1, cx);
41     auto maxs = list->at(1).to<List>(maxcx);
42     maxs->assert_size(3, maxcx);
43 
44     BBox b;
45     b.xmin = mins->at(0).to_num(cx);
46     b.ymin = mins->at(1).to_num(cx);
47     b.zmin = mins->at(2).to_num(cx);
48     b.xmax = maxs->at(0).to_num(cx);
49     b.ymax = maxs->at(1).to_num(cx);
50     b.zmax = maxs->at(2).to_num(cx);
51     return b;
52 }
53 
54 bool
recognize(Value val,Render_Opts * opts)55 Shape_Program::recognize(Value val, Render_Opts* opts)
56 {
57     static Symbol_Ref is_2d_key = make_symbol("is_2d");
58     static Symbol_Ref is_3d_key = make_symbol("is_3d");
59     static Symbol_Ref bbox_key = make_symbol("bbox");
60     static Symbol_Ref dist_key = make_symbol("dist");
61     static Symbol_Ref colour_key = make_symbol("colour");
62     static Symbol_Ref render_key = make_symbol("render");
63 
64     At_Program cx(*this);
65 
66     auto r = val.maybe<Record>();
67     if (r == nullptr) return false;
68     Value is_2d_val = r->find_field(is_2d_key, cx);
69     if (is_2d_val.is_missing()) return false;
70     Value is_3d_val = r->find_field(is_3d_key, cx);
71     if (is_3d_val.is_missing()) return false;
72     Value bbox_val = r->find_field(bbox_key, cx);
73     if (bbox_val.is_missing()) return false;
74     Value dist_val = r->find_field(dist_key, cx);
75     if (dist_val.is_missing()) return false;
76     Value colour_val = r->find_field(colour_key, cx);
77     if (colour_val.is_missing()) return false;
78 
79     is_2d_ = is_2d_val.to_bool(At_Field("is_2d", cx));
80     is_3d_ = is_3d_val.to_bool(At_Field("is_3d", cx));
81     if (!is_2d_ && !is_3d_)
82         throw Exception(cx,
83             "at least one of is_2d and is_3d must be true");
84     bbox_ = BBox::from_value(bbox_val, At_Field("bbox", cx));
85 
86     dist_fun_ = value_to_function(dist_val, At_Field("dist", cx));
87     dist_frame_ = Frame::make(dist_fun_->nslots_, sstate_, nullptr,
88         nullptr, nullptr);
89 
90     colour_fun_ = value_to_function(colour_val, At_Field("colour", cx));
91     colour_frame_ = Frame::make(colour_fun_->nslots_, sstate_, nullptr,
92         nullptr, nullptr);
93 
94     Value render_val = r->find_field(render_key, cx);
95     if (!render_val.is_missing()) {
96         At_Field rcx("render", cx);
97         auto render = render_val.to<Record>(rcx);
98         if (opts != nullptr) {
99             // update *opts from fields in the render record
100             opts->update_from_record(*render, rcx);
101         }
102     }
103 
104     record_ = r;
105     return true;
106 }
107 
Shape_Program(const Shape_Program & shape,Shared<Record> r,Viewed_Shape * vs)108 Shape_Program::Shape_Program(
109     const Shape_Program& shape,
110     Shared<Record> r,
111     Viewed_Shape* vs)
112 :
113     sstate_(shape.sstate_),
114     nub_(shape.nub_),
115     record_(r),
116     viewed_shape_(vs)
117 {
118     static Symbol_Ref dist_key = make_symbol("dist");
119     static Symbol_Ref colour_key = make_symbol("colour");
120 
121     is_2d_ = shape.is_2d_;
122     is_3d_ = shape.is_3d_;
123     bbox_ = shape.bbox_;
124 
125     At_Program cx(*this);
126 
127     if (r->hasfield(dist_key))
128         dist_fun_ = value_to_function(r->getfield(dist_key, cx), cx);
129     else
130         throw Exception{cx, stringify(
131             "bad parametric shape: call result has no 'dist' field: ", r)};
132 
133     if (r->hasfield(colour_key))
134         colour_fun_ = value_to_function(r->getfield(colour_key, cx), cx);
135     else
136         throw Exception{cx, stringify(
137             "bad parametric shape: call result has no 'colour' field: ", r)};
138 }
139 
140 double
dist(double x,double y,double z,double t)141 Shape_Program::dist(double x, double y, double z, double t)
142 {
143     At_Program cx(*this);
144     Shared<List> point = List::make({Value{x}, Value{y}, Value{z}, Value{t}});
145     Value result = dist_fun_->call({point}, Fail::hard, *dist_frame_);
146     return result.to_num(cx);
147 }
148 
149 Vec3
colour(double x,double y,double z,double t)150 Shape_Program::colour(double x, double y, double z, double t)
151 {
152     At_Program cx(*this);
153     Shared<List> point = List::make({Value{x}, Value{y}, Value{z}, Value{t}});
154     Value result = colour_fun_->call({point}, Fail::hard, *colour_frame_);
155     Shared<List> cval = result.to<List>(cx);
156     cval->assert_size(3, cx);
157     return Vec3{ cval->at(0).to_num(cx),
158                  cval->at(1).to_num(cx),
159                  cval->at(2).to_num(cx) };
160 }
161 
162 } // namespace
163