2 // nazghul - an old-school RPG engine
3 // Copyright (C) 2002, 2003 Gordon McNutt
5 // Thi program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 2 of the License, or (at your option)
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 // You should have received a copy of the GNU General Public License along with
16 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 // Suite 330, Boston, MA 02111-1307 USA
20 // gmcnutt@users.sourceforge.net
37 // Amount by which we horizontally shift the light function to make noon
38 // produce maximum light
39 #define SKY_HORZ_SHIFT (NOON_DEGREE - 90)
41 // Amount by which we vertically shift the light function to make sunrise and
42 // sunset occur at the specified times. Since this involces taking the sin
43 // of a value we must do this at runtime.
44 static double SKY_VERT_SHIFT = 0.0;
46 // Amount by which we multiple the sine factor of the light function to make it
47 // ramp up faster. Again, must do this at runtime.
48 static double SKY_AMPLITUDE = 0.0;
50 // Conversion factors for deciding where in the sky window an astral body is
52 // W = width of sky window
53 // t = width of astral body sprite
56 // P(x) = position in window of arc x
59 // +---+-------------------------------+---+
60 // | t |<------------ W -------------->| t |
61 // +---+-------------------------------+---+
68 // And solving we get:
69 // m = (W + t)/(S - R)
74 static double SKY_WIN_SLOPE;
75 static double SKY_WIN_OFFSET;
77 #define SKY_ARC_TO_PIXEL_OFFSET(arc) (int)(SKY_WIN_SLOPE * (double)(arc) + \
80 #define DEGREES_TO_RADIANS(deg) (double)((deg) * 0.0174603)
82 #define ASTRAL_BODY_ARC_WIDTH ((double)SKY_SPRITE_W/(double)SKY_WIN_SLOPE)
83 #define ECLIPSE_FACTOR 0.75
85 //////////////////////////////////////////////////////////////////////////////
87 // Internal helper functions
89 //////////////////////////////////////////////////////////////////////////////
91 static int sky_get_light_from_astral_body(int arc, int maxlight)
94 // M | ...................
97 // 0 |--------|--------|--------|--------| arc
100 // Start ramping up at sunrise, ramp down and hit zero at sunset. I
101 // think a simple way to simulate this is to take a horizontal slice
102 // out of a sine wave.
108 // |._______._______.
113 // Point 'r' is sunrise, 's' is sunset. In our system the day and night
114 // are not equal, so to simulate this we modify the basic sine function
117 // Here's sin(arc) + C
122 // |_________._____._
126 // But in our system sunrise is not at zero degrees, so we need to
127 // shift the function:
129 // sin(arc - theta) + C:
134 // |__._________.____.
138 // Since we don't want our function to produce a value greater than
139 // 1.0, we cap it, producing a flat top which represents max light. If
140 // we stop here then it takes about four hours for the sun to go from
141 // zero to max output. I'd prefer it if this were more like an hour. So
142 // for this we need to multiply the result of the sin function to
143 // increase the amplitude.
145 // A * (sin(arc - theta) + C)
150 // |__._________.____.
156 int degrees = (arc - SKY_HORZ_SHIFT);
157 double radians = DEGREES_TO_RADIANS(degrees);
158 double factor = SKY_AMPLITUDE * (sin(radians) + SKY_VERT_SHIFT);
159 clamp(factor, 0.0, 1.0);
160 int light = (int)(factor * maxlight);
161 light = (light < 0) ? 0 : light;
165 static void sky_compute_factors(void)
167 // Called once at load time. The light function is derived above as:
169 // light = A * (sin(x - theta) + C)
171 // Here we solve for C by using the sunrise time 'R', where the
172 // function passes through 0. Since the amplitude does not
174 // A * (sin(R - theta) + C) = 0
175 // C = -sin(R - theta)
177 SKY_VERT_SHIFT = -sin(DEGREES_TO_RADIANS(SUNRISE_DEGREE -
180 // Now solve for A, which is the amplitude, using the first value after
181 // sunrise for which the function produces 1.0. This value should be
182 // one hour after sunrise.
184 // A * (sin(V - theta) + C) = 1
185 // A = 1 / (sin(V - theta) + C)
187 double inverse = sin(DEGREES_TO_RADIANS(SUNRISE_DEGREE +
191 assert(inverse != 0.0);
192 SKY_AMPLITUDE = 1 / inverse;
195 static void sky_paint_astral_body(struct sky *sky, int arc,
196 struct sprite *sprite)
201 pixels = SKY_ARC_TO_PIXEL_OFFSET(arc);
202 if (pixels < 0 || pixels > SKY_W + SKY_SPRITE_W)
204 x = sky->screenRect.x + sky->screenRect.w - pixels;
206 sprite_paint(sprite, 0, x, sky->screenRect.y);
209 int astral_body_is_visible(int arc)
212 // I tested somewhat before, during, after sunset,
213 // and found that the sprite for the moon "left of the sun"
214 // became not visible (out of the drawing area?) before
215 // the AT command (which calls sky_astral_body_is_visible() )
216 // reported that a moon had set.
218 // Why the difference in apparent "set" time?
219 if (arc < SUNRISE_DEGREE || arc > (SUNSET_DEGREE + SKY_SPRITE_W))
224 //////////////////////////////////////////////////////////////////////////////
228 //////////////////////////////////////////////////////////////////////////////
230 struct astral_body *astral_body_new(char *tag, char *name, int n_phases)
232 struct astral_body *body;
237 body = (struct astral_body*)calloc(1, sizeof(*body));
240 list_init(&body->list);
242 body->n_phases = n_phases;
243 body->phases = (struct phase*)calloc(body->n_phases,
244 sizeof(struct phase));
245 assert(body->phases);
247 body->tag = strdup(tag);
250 body->name = strdup(name);
256 void astral_body_del(struct astral_body *body)
263 assert(body->phases);
265 closure_unref_safe(body->gifc);
270 for (i = 0; i < body->n_phases; i++)
271 if (body->phases[i].name)
272 free(body->phases[i].name);
277 void astral_body_save(struct astral_body *body, struct save *save)
282 save->enter(save, "(bind-astral-body\n");
284 save->enter(save, "(kern-mk-astral-body\n");
285 save->write(save, "'%s\t; tag\n", body->tag);
286 save->write(save, "\"%s\"\t; name\n", body->name);
287 save->write(save, "%d\t; distance\n", body->distance);
288 save->write(save, "%d\t; minutes_per_phase\n",
289 body->minutes_per_phase);
290 save->write(save, "%d\t; minutes_per_degress\n",
291 body->minutes_per_degree);
292 save->write(save, "%d\t; initial_arc\n", body->initial_arc);
293 save->write(save, "%d\t; initial_phase\n", body->initial_phase);
294 //save->write(save, "%d\t; n_phases\n", body->n_phases);
296 closure_save(body->gifc, save);
298 save->write(save, "nil\t; gifc\n");
299 assert(body->n_phases);
300 save->enter(save, "(list\n");
301 for (i = 0; i < body->n_phases; i++) {
302 save->write(save, "(list %s %d \"%s\")\n",
303 sprite_get_tag(body->phases[i].sprite),
304 body->phases[i].maxlight,
305 body->phases[i].name);
307 save->exit(save, ")\n");
308 save->exit(save, ")\n");
311 gob_save(body->gob, save);
312 save->exit(save, ") ;; bind-astral-body\n");
316 static void astral_body_advance_phase(struct astral_body *body)
320 assert(body->n_phases);
322 // Calculate the new phase
324 if (body->minutes_per_phase) {
325 new_phase = clock_time() / body->minutes_per_phase;
327 new_phase += body->initial_phase;
328 new_phase %= body->n_phases;
330 if (new_phase == body->phase)
333 // Run the phase-change handler in the script
335 closure_exec(body->gifc, "ypdd", "phase-change",
336 body, body->phase, new_phase);
338 body->phase = new_phase;
341 static void astral_body_advance_arc(struct astral_body *body)
346 // Calculate the new arc
347 new_arc = (clock_time() / body->minutes_per_degree);
348 new_arc += body->initial_arc;
351 if (new_arc == body->arc)
356 if (body->n_phases > 0) {
357 astral_body_advance_phase(body);
360 // Change the body's light
361 original_light = body->light;
362 body->light = sky_get_light_from_astral_body(
364 body->phases[body->phase].maxlight);
365 if (original_light != body->light)
374 static void range_init(range_t *range)
376 list_init(&range->list);
382 static range_t *range_new()
384 range_t *range = (range_t*)malloc(sizeof(*range));
390 static void range_unref(range_t *range)
398 static void range_set_value(range_t *range, int x, int w)
404 static range_t * range_intersect(range_t *r1, range_t *r2)
406 // Note: this assumes positive values all around, no modulo
410 if (r1->x >= r2->x) {
411 edge = r1->x - r1->w;
415 // --|-----|-----|-----|
419 // r1 overlaps onto r2:
424 overlap = r2->x - edge;
425 range_set_value(out, r2->x, overlap);
429 edge = r2->x - r2->w;
433 // -----|-----|----|----|
439 // r2 overlaps onto r1:
444 overlap = r1->x - edge;
445 range_set_value(out, r1->x, overlap);
449 static range_t *range_merge(range_t *r1, range_t *r2)
451 // Note: this assumes positive values all around, no modulo
455 if (r1->x >= r2->x) {
456 edge = r1->x - r1->w;
460 // --|-----|-----|-----|
464 // r1 overlaps onto r2:
469 overlap = r2->x - edge;
470 range_set_value(out, r1->x, r1->w + r2->w - overlap);
474 edge = r2->x - r2->w;
478 // -----|-----|----|----|
484 // r2 overlaps onto r1:
489 overlap = r1->x - edge;
490 range_set_value(out, r2->x, r1->w + r2->w - overlap);
494 static void range_set_union(struct list *set, range_t *r1)
503 //dbg("[%d %d]\n", r1->x-360, r1->w);
507 while (elem != set) {
509 range_t *r2 = outcast(elem, range_t, list);
512 ru = range_merge(r1, r2);
514 /* dbg(" merged: [%d %d] + [%d %d] = [%d %d]\n", */
515 /* r1->x-360, r1->w, */
516 /* r2->x-360, r2->w, */
517 /* ru->x-360, ru->w); */
519 list_remove(&r2->list);
525 list_add(set, &r1->list);
529 static int range_set_sum(struct list *set)
533 list_for_each(set, elem) {
534 range_t *rr=outcast(elem, range_t, list);
540 static void range_set_unref_elements(struct list *set)
545 while (elem != set) {
546 range_t *range = outcast(elem, range_t, list);
552 static void sky_get_eclipse(struct sky *sky,
553 struct astral_body *outer)
555 struct astral_body *inner = NULL;
556 struct list *elem = outer->list.next;
561 //dbg(" sky_get_eclipse %s (%d)\n", outer->name, outer->arc);
563 outer->eclipse = 1.0;
565 range_set_value(&r1, outer->arc+360, (int)ASTRAL_BODY_ARC_WIDTH);
567 // Check each body listed after this one to see if it eclipses it.
568 while (elem != &sky->bodies) {
572 inner = outcast(elem, struct astral_body, list);
575 //dbg(" check %s (%d)...", inner->name, inner->arc);
577 range_set_value(&r2, inner->arc+360,
578 (int)ASTRAL_BODY_ARC_WIDTH);
579 range_set_union(&ranges, range_intersect(&r1, &r2));
582 eclipse_arc = range_set_sum(&ranges);
583 outer->eclipse = eclipse_arc/ASTRAL_BODY_ARC_WIDTH * ECLIPSE_FACTOR;
584 range_set_unref_elements(&ranges);
587 //////////////////////////////////////////////////////////////////////////////
591 //////////////////////////////////////////////////////////////////////////////
593 void sky_advance(struct sky *sky, int visible)
596 struct astral_body *body;
598 screenErase(&sky->screenRect);
600 //dbg("sky_advance\n");
601 list_for_each(&sky->bodies, elem) {
602 body = outcast(elem, struct astral_body, list);
603 astral_body_advance_arc(body);
606 sky_paint_astral_body(sky, body->arc,
607 body->phases[body->phase].sprite);
609 // Assume the bodies are listed in order from outermost to
610 // innermost; outer bodies will be eclipsed by inner ones
611 sky_get_eclipse(sky, body);
614 screenUpdate(&sky->screenRect);
617 void sky_init(struct sky *sky)
619 sky->screenRect.w = SKY_W;
620 sky->screenRect.x = SKY_X;
621 sky->screenRect.y = SKY_Y;
622 sky->screenRect.h = SKY_H;
624 SKY_WIN_SLOPE = ((double)SKY_W + (double)SKY_SPRITE_W) /
625 ((double)SUNSET_DEGREE - (double)SUNRISE_DEGREE);
626 SKY_WIN_OFFSET = -(double)SUNRISE_DEGREE * (double)SKY_WIN_SLOPE;
628 sky_compute_factors();
629 list_init(&sky->bodies);
632 void sky_start_session(struct sky *sky, int visible)
634 sky_advance(sky, visible);
637 void sky_end_session(struct sky *sky)
640 struct astral_body *body;
642 elem = sky->bodies.next;
643 while (elem != &sky->bodies) {
644 body = outcast(elem, struct astral_body, list);
646 astral_body_del(body);
650 void sky_add_astral_body(struct sky *sky, struct astral_body *body)
652 // Need to keep them in order by astronomical distance, furthest to
653 // closest, so the sprites are rendered in the right order.
655 struct astral_body *other;
657 elem = sky->bodies.next;
658 while (elem != &sky->bodies) {
659 other = outcast(elem, struct astral_body, list);
660 // Find the first body closer than this one and insert this one
662 if (other->distance < body->distance)
667 list_add(elem->prev, &body->list);
670 int sky_get_ambient_light(struct sky *sky)
673 struct astral_body *body;
676 list_for_each(&sky->bodies, elem) {
677 body = outcast(elem, struct astral_body, list);
678 light += (int)((float)body->light * (1.0 - body->eclipse));
681 return clamp(light, 0, MAX_AMBIENT_LIGHT);
684 void sky_save(struct sky *sky, struct save *save)
687 struct astral_body *body;
689 save->write(save, ";; ---------\n");
690 save->write(save, ";; Astronomy\n");
691 save->write(save, ";; ---------\n");
693 list_for_each(&sky->bodies, elem) {
694 body = outcast(elem, struct astral_body, list);
695 astral_body_save(body, save);