2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
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.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2012 Blender Foundation.
19 * All rights reserved.
22 * Contributor(s): Blender Foundation,
26 * ***** END GPL LICENSE BLOCK *****
29 /** \file blender/editors/mask/mask_draw.c
33 #include "MEM_guardedalloc.h"
35 #include "BLI_utildefines.h"
40 #include "BKE_context.h"
43 #include "DNA_mask_types.h"
44 #include "DNA_screen_types.h"
45 #include "DNA_object_types.h" /* SELECT */
46 #include "DNA_space_types.h"
49 #include "ED_mask.h" /* own include */
50 #include "ED_space_api.h"
52 #include "BIF_glutil.h"
54 #include "UI_resources.h"
55 #include "UI_view2d.h"
57 #include "mask_intern.h" /* own include */
59 static void mask_spline_color_get(MaskLayer *masklay, MaskSpline *spline, const bool is_sel,
60 unsigned char r_rgb[4])
63 if (masklay->act_spline == spline) {
64 r_rgb[0] = r_rgb[1] = r_rgb[2] = 255;
68 r_rgb[1] = r_rgb[2] = 0;
73 r_rgb[1] = r_rgb[2] = 0;
79 static void mask_spline_feather_color_get(MaskLayer *UNUSED(masklay), MaskSpline *UNUSED(spline), const bool is_sel,
80 unsigned char r_rgb[4])
84 r_rgb[0] = r_rgb[2] = 0;
88 r_rgb[0] = r_rgb[2] = 0;
95 static void draw_spline_parents(MaskLayer *UNUSED(masklay), MaskSpline *spline)
98 MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
100 if (!spline->tot_point)
104 glEnable(GL_LINE_STIPPLE);
105 glLineStipple(1, 0xAAAA);
109 for (i = 0; i < spline->tot_point; i++) {
110 MaskSplinePoint *point = &points_array[i];
111 BezTriple *bezt = &point->bezt;
113 if (point->parent.id) {
114 glVertex2f(bezt->vec[1][0],
117 glVertex2f(bezt->vec[1][0] - point->parent.offset[0],
118 bezt->vec[1][1] - point->parent.offset[1]);
124 glDisable(GL_LINE_STIPPLE);
128 static void mask_point_undistort_pos(SpaceClip *sc, float r_co[2], const float co[2])
130 BKE_mask_coord_to_movieclip(sc->clip, &sc->user, r_co, co);
131 ED_clip_point_undistorted_pos(sc, r_co, r_co);
132 BKE_mask_coord_from_movieclip(sc->clip, &sc->user, r_co, r_co);
135 static void draw_circle(const float x, const float y,
136 const float size, const float xscale, const float yscale)
138 static GLuint displist = 0;
140 /* Initialize round circle shape. */
144 displist = glGenLists(1);
145 glNewList(displist, GL_COMPILE);
147 qobj = gluNewQuadric();
148 gluQuadricDrawStyle(qobj, GLU_SILHOUETTE);
149 gluDisk(qobj, 0, 0.7, 8, 1);
150 gluDeleteQuadric(qobj);
156 glTranslatef(x, y, 0.0f);
157 glScalef(1.0f / xscale * size, 1.0f / yscale * size, 1.0f);
158 glCallList(displist);
162 static void draw_single_handle(const MaskLayer *mask_layer, const MaskSplinePoint *point,
163 const eMaskWhichHandle which_handle, const int draw_type,
164 const float handle_size, const float xscale, const float yscale,
165 const float point_pos[2], const float handle_pos[2])
167 const BezTriple *bezt = &point->bezt;
170 if (which_handle == MASK_WHICH_HANDLE_STICK || which_handle == MASK_WHICH_HANDLE_LEFT) {
171 handle_type = bezt->h1;
174 handle_type = bezt->h2;
177 if (handle_type == HD_VECT) {
181 /* this could be split into its own loop */
182 if (draw_type == MASK_DT_OUTLINE) {
183 const unsigned char rgb_gray[4] = {0x60, 0x60, 0x60, 0xff};
185 glColor4ubv(rgb_gray);
187 glVertex2fv(point_pos);
188 glVertex2fv(handle_pos);
193 switch (handle_type) {
195 UI_ThemeColor(TH_HANDLE_FREE);
198 UI_ThemeColor(TH_HANDLE_AUTO);
201 case HD_ALIGN_DOUBLESIDE:
202 UI_ThemeColor(TH_HANDLE_ALIGN);
207 glVertex2fv(point_pos);
208 glVertex2fv(handle_pos);
211 /* draw handle points */
212 if (MASKPOINT_ISSEL_HANDLE(point, which_handle)) {
213 if (point == mask_layer->act_point)
214 glColor3f(1.0f, 1.0f, 1.0f);
216 glColor3f(1.0f, 1.0f, 0.0f);
219 glColor3f(0.5f, 0.5f, 0.0f);
222 draw_circle(handle_pos[0], handle_pos[1], handle_size, xscale, yscale);
225 /* return non-zero if spline is selected */
226 static void draw_spline_points(const bContext *C, MaskLayer *masklay, MaskSpline *spline,
227 const char draw_flag, const char draw_type,
228 const float xscale, const float yscale)
230 const bool is_spline_sel = (spline->flag & SELECT) && (masklay->restrictflag & MASK_RESTRICT_SELECT) == 0;
231 const bool is_smooth = (draw_flag & MASK_DRAWFLAG_SMOOTH) != 0;
233 unsigned char rgb_spline[4];
234 MaskSplinePoint *points_array = BKE_mask_spline_point_array(spline);
235 SpaceClip *sc = CTX_wm_space_clip(C);
236 bool undistort = false;
238 int i, handle_size, tot_feather_point;
239 float (*feather_points)[2], (*fp)[2];
241 if (!spline->tot_point)
245 undistort = sc->clip && (sc->user.render_flag & MCLIP_PROXY_RENDER_UNDISTORT) != 0;
247 /* TODO, add this to sequence editor */
248 handle_size = UI_GetThemeValuef(TH_HANDLE_VERTEX_SIZE) * U.pixelsize;
250 glPointSize(handle_size);
252 mask_spline_color_get(masklay, spline, is_spline_sel, rgb_spline);
255 feather_points = fp = BKE_mask_spline_feather_points(spline, &tot_feather_point);
256 for (i = 0; i < spline->tot_point; i++) {
258 /* watch it! this is intentionally not the deform array, only check for sel */
259 MaskSplinePoint *point = &spline->points[i];
263 for (j = 0; j <= point->tot_uw; j++) {
264 float feather_point[2];
267 copy_v2_v2(feather_point, *fp);
270 mask_point_undistort_pos(sc, feather_point, feather_point);
273 sel = MASKPOINT_ISSEL_ANY(point);
276 sel = point->uw[j - 1].flag & SELECT;
280 if (point == masklay->act_point)
281 glColor3f(1.0f, 1.0f, 1.0f);
283 glColor3f(1.0f, 1.0f, 0.0f);
286 glColor3f(0.5f, 0.5f, 0.0f);
290 glVertex2fv(feather_point);
296 MEM_freeN(feather_points);
299 glEnable(GL_LINE_SMOOTH);
301 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
305 for (i = 0; i < spline->tot_point; i++) {
307 /* watch it! this is intentionally not the deform array, only check for sel */
308 MaskSplinePoint *point = &spline->points[i];
309 MaskSplinePoint *point_deform = &points_array[i];
310 BezTriple *bezt = &point_deform->bezt;
314 copy_v2_v2(vert, bezt->vec[1]);
317 mask_point_undistort_pos(sc, vert, vert);
320 /* draw handle segment */
321 if (BKE_mask_point_handles_mode_get(point) == MASK_HANDLE_MODE_STICK) {
323 BKE_mask_point_handle(point_deform, MASK_WHICH_HANDLE_STICK, handle);
325 mask_point_undistort_pos(sc, handle, handle);
327 draw_single_handle(masklay, point, MASK_WHICH_HANDLE_STICK,
328 draw_type, handle_size, xscale, yscale, vert, handle);
331 float handle_left[2], handle_right[2];
332 BKE_mask_point_handle(point_deform, MASK_WHICH_HANDLE_LEFT, handle_left);
333 BKE_mask_point_handle(point_deform, MASK_WHICH_HANDLE_RIGHT, handle_right);
335 mask_point_undistort_pos(sc, handle_left, handle_left);
336 mask_point_undistort_pos(sc, handle_left, handle_left);
338 draw_single_handle(masklay, point, MASK_WHICH_HANDLE_LEFT,
339 draw_type, handle_size, xscale, yscale, vert, handle_left);
340 draw_single_handle(masklay, point, MASK_WHICH_HANDLE_RIGHT,
341 draw_type, handle_size, xscale, yscale, vert, handle_right);
345 if (MASKPOINT_ISSEL_KNOT(point)) {
346 if (point == masklay->act_point)
347 glColor3f(1.0f, 1.0f, 1.0f);
349 glColor3f(1.0f, 1.0f, 0.0f);
352 glColor3f(0.5f, 0.5f, 0.0f);
362 glDisable(GL_LINE_SMOOTH);
367 /* #define USE_XOR */
369 static void mask_color_active_tint(unsigned char r_rgb[4], const unsigned char rgb[4], const short is_active)
372 r_rgb[0] = (unsigned char)((((int)(rgb[0])) + 128) / 2);
373 r_rgb[1] = (unsigned char)((((int)(rgb[1])) + 128) / 2);
374 r_rgb[2] = (unsigned char)((((int)(rgb[2])) + 128) / 2);
378 *(unsigned int *)r_rgb = *(const unsigned int *)rgb;
382 static void mask_draw_curve_type(const bContext *C, MaskSpline *spline, float (*orig_points)[2], int tot_point,
383 const bool is_feather, const bool is_smooth, const bool is_active,
384 const unsigned char rgb_spline[4], const char draw_type)
386 const int draw_method = (spline->flag & MASK_SPLINE_CYCLIC) ? GL_LINE_LOOP : GL_LINE_STRIP;
387 const unsigned char rgb_black[4] = {0x00, 0x00, 0x00, 0xff};
388 // const unsigned char rgb_white[4] = {0xff, 0xff, 0xff, 0xff};
389 unsigned char rgb_tmp[4];
390 SpaceClip *sc = CTX_wm_space_clip(C);
391 float (*points)[2] = orig_points;
394 int undistort = sc->clip && sc->user.render_flag & MCLIP_PROXY_RENDER_UNDISTORT;
399 points = MEM_callocN(2 * tot_point * sizeof(float), "undistorthed mask curve");
401 for (i = 0; i < tot_point; i++) {
402 mask_point_undistort_pos(sc, points[i], orig_points[i]);
407 glEnableClientState(GL_VERTEX_ARRAY);
408 glVertexPointer(2, GL_FLOAT, 0, points);
412 case MASK_DT_OUTLINE:
415 mask_color_active_tint(rgb_tmp, rgb_black, is_active);
416 glColor4ubv(rgb_tmp);
418 glDrawArrays(draw_method, 0, tot_point);
421 mask_color_active_tint(rgb_tmp, rgb_spline, is_active);
422 glColor4ubv(rgb_tmp);
423 glDrawArrays(draw_method, 0, tot_point);
429 glEnable(GL_LINE_STIPPLE);
432 glEnable(GL_COLOR_LOGIC_OP);
435 mask_color_active_tint(rgb_tmp, rgb_spline, is_active);
436 glColor4ubv(rgb_tmp);
437 glLineStipple(3, 0xaaaa);
438 glEnableClientState(GL_VERTEX_ARRAY);
439 glVertexPointer(2, GL_FLOAT, 0, points);
440 glDrawArrays(draw_method, 0, tot_point);
443 glDisable(GL_COLOR_LOGIC_OP);
445 mask_color_active_tint(rgb_tmp, rgb_black, is_active);
446 glColor4ubv(rgb_tmp);
447 glLineStipple(3, 0x5555);
448 glDrawArrays(draw_method, 0, tot_point);
450 glDisable(GL_LINE_STIPPLE);
456 if (draw_type == MASK_DT_BLACK) { rgb_tmp[0] = rgb_tmp[1] = rgb_tmp[2] = 0; }
457 else { rgb_tmp[0] = rgb_tmp[1] = rgb_tmp[2] = 255; }
458 /* alpha values seem too low but gl draws many points that compensate for it */
459 if (is_feather) { rgb_tmp[3] = 64; }
460 else { rgb_tmp[3] = 128; }
463 rgb_tmp[0] = (unsigned char)(((short)rgb_tmp[0] + (short)rgb_spline[0]) / 2);
464 rgb_tmp[1] = (unsigned char)(((short)rgb_tmp[1] + (short)rgb_spline[1]) / 2);
465 rgb_tmp[2] = (unsigned char)(((short)rgb_tmp[2] + (short)rgb_spline[2]) / 2);
468 if (is_smooth == FALSE && is_feather) {
470 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
473 mask_color_active_tint(rgb_tmp, rgb_tmp, is_active);
474 glColor4ubv(rgb_tmp);
476 glEnableClientState(GL_VERTEX_ARRAY);
477 glVertexPointer(2, GL_FLOAT, 0, points);
478 glDrawArrays(draw_method, 0, tot_point);
480 if (is_smooth == FALSE && is_feather) {
487 glDisableClientState(GL_VERTEX_ARRAY);
489 if (points != orig_points)
493 static void draw_spline_curve(const bContext *C, MaskLayer *masklay, MaskSpline *spline,
494 const char draw_flag, const char draw_type,
495 const bool is_active,
496 const int width, const int height)
498 const unsigned int resol = max_ii(BKE_mask_spline_feather_resolution(spline, width, height),
499 BKE_mask_spline_resolution(spline, width, height));
501 unsigned char rgb_tmp[4];
503 const bool is_spline_sel = (spline->flag & SELECT) && (masklay->restrictflag & MASK_RESTRICT_SELECT) == 0;
504 const bool is_smooth = (draw_flag & MASK_DRAWFLAG_SMOOTH) != 0;
505 const bool is_fill = (spline->flag & MASK_SPLINE_NOFILL) == 0;
507 unsigned int tot_diff_point;
508 float (*diff_points)[2];
510 unsigned int tot_feather_point;
511 float (*feather_points)[2];
513 diff_points = BKE_mask_spline_differentiate_with_resolution(spline, &tot_diff_point, resol);
519 glEnable(GL_LINE_SMOOTH);
521 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
524 feather_points = BKE_mask_spline_feather_differentiated_points_with_resolution(spline, &tot_feather_point, resol, (is_fill != FALSE));
527 mask_spline_feather_color_get(masklay, spline, is_spline_sel, rgb_tmp);
528 mask_draw_curve_type(C, spline, feather_points, tot_feather_point,
529 TRUE, is_smooth, is_active,
534 float *fp = &diff_points[0][0];
535 float *fp_feather = &feather_points[0][0];
539 BLI_assert(tot_diff_point == tot_feather_point);
541 for (i = 0; i < tot_diff_point; i++, fp += 2, fp_feather += 2) {
542 sub_v2_v2v2(tvec, fp, fp_feather);
543 add_v2_v2v2(fp_feather, fp, tvec);
547 mask_draw_curve_type(C, spline, feather_points, tot_feather_point,
548 TRUE, is_smooth, is_active,
552 MEM_freeN(feather_points);
554 /* draw main curve */
555 mask_spline_color_get(masklay, spline, is_spline_sel, rgb_tmp);
556 mask_draw_curve_type(C, spline, diff_points, tot_diff_point,
557 FALSE, is_smooth, is_active,
559 MEM_freeN(diff_points);
561 if (draw_flag & MASK_DRAWFLAG_SMOOTH) {
562 glDisable(GL_LINE_SMOOTH);
569 static void draw_masklays(const bContext *C, Mask *mask, const char draw_flag, const char draw_type,
570 const int width, const int height, const float xscale, const float yscale)
575 for (masklay = mask->masklayers.first, i = 0; masklay; masklay = masklay->next, i++) {
577 const bool is_active = (i == mask->masklay_act);
579 if (masklay->restrictflag & MASK_RESTRICT_VIEW) {
583 for (spline = masklay->splines.first; spline; spline = spline->next) {
585 /* draw curve itself first... */
586 draw_spline_curve(C, masklay, spline, draw_flag, draw_type, is_active, width, height);
588 // draw_spline_parents(masklay, spline);
590 if (!(masklay->restrictflag & MASK_RESTRICT_SELECT)) {
591 /* ...and then handles over the curve so they're nicely visible */
592 draw_spline_points(C, masklay, spline, draw_flag, draw_type, xscale, yscale);
595 /* show undeform for testing */
597 void *back = spline->points_deform;
599 spline->points_deform = NULL;
600 draw_spline_curve(C, masklay, spline, draw_flag, draw_type, is_active, width, height);
601 // draw_spline_parents(masklay, spline);
602 draw_spline_points(C, masklay, spline, draw_flag, draw_type, xscale, yscale);
603 spline->points_deform = back;
609 void ED_mask_draw(const bContext *C,
610 const char draw_flag, const char draw_type)
612 ScrArea *sa = CTX_wm_area(C);
613 ARegion *ar = CTX_wm_region(C);
615 Mask *mask = CTX_data_edit_mask(C);
618 float xscale, yscale;
623 ED_mask_get_size(sa, &width, &height);
624 ED_mask_get_aspect(sa, ar, &aspx, &aspy);
625 UI_view2d_getscale(&ar->v2d, &xscale, &yscale);
627 draw_masklays(C, mask, draw_flag, draw_type, width, height, xscale * aspx, yscale * aspy);
630 typedef struct ThreadedMaskRasterizeState {
631 MaskRasterHandle *handle;
634 } ThreadedMaskRasterizeState;
636 typedef struct ThreadedMaskRasterizeData {
639 } ThreadedMaskRasterizeData;
641 static void mask_rasterize_func(TaskPool *pool, void *taskdata, int UNUSED(threadid))
643 ThreadedMaskRasterizeState *state = (ThreadedMaskRasterizeState *) BLI_task_pool_userdata(pool);
644 ThreadedMaskRasterizeData *data = (ThreadedMaskRasterizeData *) taskdata;
647 for (scanline = 0; scanline < data->num_scanlines; scanline++) {
648 int x, y = data->start_scanline + scanline;
649 for (x = 0; x < state->width; x++) {
650 int index = y * state->width + x;
653 xy[0] = (float) x / state->width;
654 xy[1] = (float) y / state->height;
656 state->buffer[index] = BKE_maskrasterize_handle_sample(state->handle, xy);
661 static float *threaded_mask_rasterize(Mask *mask, const int width, const int height)
663 TaskScheduler *task_scheduler = BLI_task_scheduler_get();
665 MaskRasterHandle *handle;
666 ThreadedMaskRasterizeState state;
668 int i, num_threads = BLI_task_scheduler_num_threads(task_scheduler), scanlines_per_thread;
670 buffer = MEM_mallocN(sizeof(float) * height * width, "rasterized mask buffer");
672 /* Initialize rasterization handle. */
673 handle = BKE_maskrasterize_handle_new();
674 BKE_maskrasterize_handle_init(handle, mask, width, height, TRUE, TRUE, TRUE);
676 state.handle = handle;
677 state.buffer = buffer;
679 state.height = height;
681 task_pool = BLI_task_pool_create(task_scheduler, &state);
683 scanlines_per_thread = height / num_threads;
684 for (i = 0; i < num_threads; i++) {
685 ThreadedMaskRasterizeData *data = MEM_mallocN(sizeof(ThreadedMaskRasterizeData),
686 "threaded mask rasterize data");
688 data->start_scanline = i * scanlines_per_thread;
690 if (i < num_threads - 1) {
691 data->num_scanlines = scanlines_per_thread;
694 data->num_scanlines = height - data->start_scanline;
697 BLI_task_pool_push(task_pool, mask_rasterize_func, data, true, TASK_PRIORITY_LOW);
700 /* work and wait until tasks are done */
701 BLI_task_pool_work_and_wait(task_pool);
704 BLI_task_pool_free(task_pool);
705 BKE_maskrasterize_handle_free(handle);
710 /* sets up the opengl context.
711 * width, height are to match the values from ED_mask_get_size() */
712 void ED_mask_draw_region(Mask *mask, ARegion *ar,
713 const char draw_flag, const char draw_type, const char overlay_mode,
714 const int width_i, const int height_i, /* convert directly into aspect corrected vars */
715 const float aspx, const float aspy,
716 const bool do_scale_applied, const bool do_draw_cb,
717 float stabmat[4][4], /* optional - only used by clip */
718 const bContext *C /* optional - only used when do_post_draw is set or called from clip editor */
721 struct View2D *v2d = &ar->v2d;
723 /* aspect always scales vertically in movie and image spaces */
724 const float width = width_i, height = (float)height_i * (aspy / aspx);
734 /* find window pixel coordinates of origin */
735 UI_view2d_to_region_no_clip(&ar->v2d, 0.0f, 0.0f, &x, &y);
738 /* w = BLI_rctf_size_x(&v2d->tot); */
739 /* h = BLI_rctf_size_y(&v2d->tot);/*/
742 zoomx = (float)(BLI_rcti_size_x(&ar->winrct) + 1) / BLI_rctf_size_x(&ar->v2d.cur);
743 zoomy = (float)(BLI_rcti_size_y(&ar->winrct) + 1) / BLI_rctf_size_y(&ar->v2d.cur);
745 if (do_scale_applied) {
750 x += v2d->tot.xmin * zoomx;
751 y += v2d->tot.ymin * zoomy;
753 /* frame the image */
754 maxdim = max_ff(width, height);
755 if (width == height) {
758 else if (width < height) {
759 xofs = ((height - width) / -2.0f) * zoomx;
762 else { /* (width > height) */
764 yofs = ((width - height) / -2.0f) * zoomy;
767 if (draw_flag & MASK_DRAWFLAG_OVERLAY) {
768 float *buffer = threaded_mask_rasterize(mask, width, height);
771 if (overlay_mode == MASK_OVERLAY_ALPHACHANNEL) {
772 glColor3f(1.0f, 1.0f, 1.0f);
773 format = GL_LUMINANCE;
776 /* More blending types could be supported in the future. */
778 glBlendFunc(GL_DST_COLOR, GL_SRC_ALPHA);
783 glTranslatef(x, y, 0);
784 glScalef(zoomx, zoomy, 0);
786 glMultMatrixf(stabmat);
788 glaDrawPixelsTex(0.0f, 0.0f, width, height, format, GL_FLOAT, GL_NEAREST, buffer);
791 if (overlay_mode != MASK_OVERLAY_ALPHACHANNEL) {
798 /* apply transformation so mask editing tools will assume drawing from the origin in normalized space */
800 glTranslatef(x + xofs, y + yofs, 0);
801 glScalef(maxdim * zoomx, maxdim * zoomy, 0);
804 glMultMatrixf(stabmat);
808 ED_region_draw_cb_draw(C, ar, REGION_DRAW_PRE_VIEW);
812 draw_masklays(C, mask, draw_flag, draw_type, width, height, maxdim * zoomx, maxdim * zoomy);
815 ED_region_draw_cb_draw(C, ar, REGION_DRAW_POST_VIEW);
821 void ED_mask_draw_frames(Mask *mask, ARegion *ar, const int cfra, const int sfra, const int efra)
823 const float framelen = ar->winx / (float)(efra - sfra + 1);
825 MaskLayer *masklay = BKE_mask_layer_active(mask);
828 glColor4ub(255, 175, 0, 255);
831 MaskLayerShape *masklay_shape;
833 for (masklay_shape = masklay->splines_shapes.first;
835 masklay_shape = masklay_shape->next)
837 int frame = masklay_shape->frame;
839 /* draw_keyframe(i, CFRA, sfra, framelen, 1); */
840 int height = (frame == cfra) ? 22 : 10;
841 int x = (frame - sfra) * framelen;
843 glVertex2i(x, height);