one shot and other fixes
[swamp-bikeopera:code.git] / lib / map.lua
1 -- Swamp Bike Opera embedded system for Kaffe Matthews
2 -- Copyright (C) 2012 Wolfgang Hauptfleisch, Dave Griffiths
3 --
4 -- This program is free software: you can redistribute it and/or modify
5 -- it under the terms of the GNU General Public License as published by
6 -- the Free Software Foundation, either version 3 of the License, or
7 -- (at your option) any later version.
8 --
9 -- This program is distributed in the hope that it will be useful,
10 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
11 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 -- GNU General Public License for more details.
13 --
14 -- You should have received a copy of the GNU General Public License
15 -- along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 module("map", package.seeall)
18
19 require 'std'
20 require 'poly'
21 require 'json'
22 require 'utils'
23 require 'posix'
24 require 'engine'
25 require 'utils'
26
27 local close_distance=0.0015
28
29 function blend_zone(zone,t)
30     --print("t="..t)
31     local poly=poly.blend_polygon(zone.vertices[1],
32                                   zone.vertices[2], t)
33     --utils.table_print(poly)
34     new_zone=utils.deepcopy(zone)
35     if poly then
36         new_zone.vertices={poly}
37     else
38         engine.log("vertex count wrong for "..zone.name..": "..#zone.vertices[1]..
39                    " vs "..#zone.vertices[2])
40     end
41     return new_zone
42 end
43
44 function resolve_ghost_zones(pos,layer,t)
45     -- first isolate the ghost zones
46     local post_ghost={}
47     local ghosts={}
48     for zone_index,zone in pairs(layer.zones) do
49         if utils.find_value("Sample Parameters:Ghost",zone.categories) then
50             table.insert(post_ghost,blend_zone(zone,t))
51         else
52             table.insert(post_ghost,zone)
53         end
54     end
55     return post_ghost
56 end
57
58 -- do a position lookup in each zone in the layer
59 -- returns list of zones we are in
60 function get_zones_from_layer(pos,layer,t)
61     local zones=resolve_ghost_zones(pos,layer,t)
62     local zones_found={}
63     for zone_index,zone in pairs(zones) do
64         if poly.is_in_polygon(zone.vertices[1], pos.lat , pos.lng) then
65            table.insert(zones_found,zone)
66         end
67     end
68     return zones_found
69 end
70
71 -- search the current layer for events
72 function get_events_from_layer(pos,state,layer,events,new_state,t)
73     local current_zones=get_zones_from_layer(pos,layer,t)
74     local last_zone_names=state[layer.name]
75
76     -- for the first time round
77     if not last_zone_names then
78         last_zone_names={}
79     end
80
81     local new_events={}
82     local current_zone_names={}
83
84     -- check for new zones
85     for i,zone in pairs(current_zones) do
86         -- if the current location was not seen last time
87         if not utils.find_value(zone.name,last_zone_names) then
88                 print("--------------------- playing "..zone.name.." -------")
89             table.insert(new_events,{type="entered-zone",
90                                      layer_name=layer.name,
91                                      zone_name=zone.name,
92                                      zone_colour=zone.colour,
93                                      zone_categories=zone.categories})
94         end
95         -- build list of names
96         table.insert(current_zone_names,zone.name)
97     end
98
99     -- check for zones we have left
100     for i,zone_name in pairs(last_zone_names) do
101         if not utils.find_value(zone_name,current_zone_names) then
102             table.insert(new_events,{type="left-zone",
103                                      layer_name=layer.name,
104                                      zone_name=zone_name,
105                                      zone_colour="",
106                                      zone_categories={"None"}})
107         end
108     end
109
110     -- update the state
111     new_state[layer.name]=current_zone_names
112     events[layer.name]=new_events
113
114     return events,new_state
115 end
116
117 ----------------------------------------------------------------------------
118 -- proximity events - loading/unloading samples based on radius
119
120 function get_prox_zones_from_layer(pos,layer,t)
121     local zones=resolve_ghost_zones(pos,layer,t)
122     local zones_found={}
123     for zone_index,zone in pairs(zones) do
124         if poly.is_close_polygon(zone.vertices[1], pos.lat , pos.lng, close_distance) then
125            table.insert(zones_found,zone)
126         end
127     end
128     return zones_found
129 end
130
131 -- search the current layer for sample load/unload events
132 function get_prox_samples_from_layer(pos,state,layer,events,new_state,t)
133     local current_zones=get_prox_zones_from_layer(pos,layer,t)
134     local last_zone_names=state[layer.name]
135
136     -- for the first time round
137     if not last_zone_names then
138         last_zone_names={}
139     end
140
141     local new_events={}
142     local current_zone_names={}
143
144     -- check for new zones
145     for i,zone in pairs(current_zones) do
146         -- if the current location was not seen last time
147         if not utils.find_value(zone.name,last_zone_names) then
148             print("--------------------- entering area around "..zone.name.." for loading -------")
149             table.insert(new_events,{type="entered-zone",
150                                      layer_name=layer.name,
151                                      zone_name=zone.name,
152                                      zone_colour=zone.colour})
153         end
154         -- build list of names
155         table.insert(current_zone_names,zone.name)
156     end
157
158     -- check for zones we have left
159     for i,zone_name in pairs(last_zone_names) do
160         if not utils.find_value(zone_name,current_zone_names) then
161             table.insert(new_events,{type="left-zone",
162                                      layer_name=layer.name,
163                                      zone_name=zone_name,
164                                      zone_colour=""})
165         end
166     end
167
168     -- update the state
169     new_state[layer.name]=current_zone_names
170     events[layer.name]=new_events
171
172     return events,new_state
173 end
174
175 -- returns a new state and a list of events
176 function get_sample_events(pos,state,map,t)
177     local events={}
178     local new_state={}
179     -- for each layer
180     for layer_index,layer in pairs(map) do
181         -- collect events and state from each layer
182         events,new_state=get_prox_samples_from_layer(pos,state,layer,events,new_state,t)
183     end
184     return new_state,events
185 end
186
187
188 -- state is a table mapping layer names to lists of last zones we are in
189 -- events is a table mapping layer names to lists of zones left and entered
190
191 -- returns a new state and a list of events
192 function get_events(pos,state,map,t)
193     local events={}
194     local new_state={}
195     -- for each layer
196     for layer_index,layer in pairs(map) do
197         -- collect events and state from each layer
198         events,new_state=get_events_from_layer(pos,state,layer,events,new_state,t)
199     end
200     return new_state,events
201 end
202
203 function test_map(map)
204     for layer_index,layer in pairs(map) do
205         for zone_index,zone in pairs(layer.zones) do
206             print("checking: "..zone.name)
207             local file=CONFIG.sound_path..zone.name..".wav";
208             -- utils.table_print(zone)
209
210             if utils.find_value("Sample Parameters:Ghost",zone.categories) then
211                 print("is ghost")
212
213                 print(#zone.vertices[1])
214                 print(#zone.vertices[2])
215                 if #zone.vertices[1] ~= #zone.vertices[2] then
216                    print("ERROR WRONG VERTEX COUNT, TELL DAVE")
217                 end
218             end
219
220             if posix.stat(file) then
221                 print("map check found: "..zone.name)
222             else
223                 print("ERROR - map check failed to find: "..file)
224             end
225         end
226     end
227 end
228
229
230 function test()
231     local map=utils.load_json("../../maps/penryn-test.json")
232     state,events=get_events({lat=50.1729952601643,lng=-5.106239318847656},{},map)
233     table_print(events)
234     state,events=get_events({lat=50.1729952601643,lng=-5.106239318847656},
235                         state,map)
236     table_print(events)
237     state,events=get_events({lat=54.1729952601643,lng=-5.106239318847656},
238                         state,map)
239     table_print(events)
240 end
241
242 --test()