gimp/plug-ins/common/sphere-designer.c
Ondřej Míchal 47f2a591f8 plug-ins/sphere-designer: Port GimpSpinEntry to GimpSpinScale
GimpSpinEntry is problematic to use when there's little horizontal space
because the slider becomes too small. GimpSpinScale does not suffer from
this because it uses the available space more efficiently. These days it
is also used more over GimpSpinEntry.

Related: https://gitlab.gnome.org/Teams/GIMP/Design/gimp-ux/-/issues/503
2025-06-24 17:37:48 +00:00

3261 lines
80 KiB
C

/*
* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* SphereDesigner v0.4 - creates textured spheres
* by Vidar Madsen <vidar@prosalg.no>
*
* Status: Last updated 1999-09-11
*
* Known issues:
* - Might crash if you click OK or Cancel before first preview is rendered
* - Phong might look weird with transparent textures
*
* Todo:
* - Saving / Loading of presets needs an overhaul
* - Antialiasing
* - Global controls: Gamma, ++
* - Beautification of GUI
* - Clean up messy source (lots of Glade remnants)
* - (Probably more. ;-)
*/
#include "config.h"
#include <string.h>
#include <errno.h>
#include <glib/gstdio.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#define PLUG_IN_PROC "plug-in-spheredesigner"
#define PLUG_IN_BINARY "sphere-designer"
#define PLUG_IN_ROLE "gimp-sphere-designer"
#define RESPONSE_RESET 1
#define PREVIEWSIZE 150
/* These must be adjusted as more functionality is added */
#define MAXOBJECT 5
#define MAXLIGHT 5
#define MAXTEXTURE 20
#define MAXTEXTUREPEROBJ 20
#define MAXNORMAL 20
#define MAXNORMALPEROBJ 20
#define MAXATMOS 1
#define MAXCOLPERGRADIENT 5
enum
{
TRIANGLE,
DISC,
PLANE,
SPHERE,
CYLINDER,
LIGHT
};
enum
{
SOLID,
CHECKER,
MARBLE,
LIZARD,
IMAGE,
PHONG,
REFLECTION,
REFRACTION,
PERLIN,
WOOD,
TRANSPARENT,
SPIRAL,
SPOTS,
SMOKE
};
enum
{
PERSPECTIVE,
ORTHOGONAL,
FISHEYE
};
enum
{
FOG
};
enum
{
TYPE,
TEXTURE,
NUM_COLUMNS
};
/* World-flags */
#define SMARTAMBIENT 0x00000001
/* Object-flags */
#define NOSHADOW 0x00000001
/* Texture-flags */
#define GRADIENT 0x00000001
typedef struct
{
gshort xsize, ysize;
guchar *rgb;
} image;
typedef struct
{
gshort numcol;
gdouble pos[MAXCOLPERGRADIENT];
GimpVector4 color[MAXCOLPERGRADIENT];
} gradient;
typedef struct
{
gint majtype;
gint type;
gulong flags;
GimpVector4 color1, color2;
gradient gradient;
GimpVector4 ambient, diffuse;
gdouble oscale;
GimpVector4 scale, translate, rotate;
image image;
GimpVector4 reflection;
GimpVector4 refraction;
GimpVector4 transparent;
gdouble ior;
GimpVector4 phongcolor;
gdouble phongsize;
gdouble amount;
gdouble exp;
GimpVector4 turbulence;
} texture;
typedef struct
{
gshort type;
gdouble density;
GimpVector4 color;
gdouble turbulence;
} atmos;
typedef struct
{
gshort type;
gulong flags;
gshort numtexture;
texture texture[MAXTEXTUREPEROBJ];
gshort numnormal;
texture normal[MAXNORMALPEROBJ];
} common;
typedef struct
{
common com;
GimpVector4 a, b, c;
} triangle;
typedef struct
{
common com;
GimpVector4 a;
gdouble b, r;
} disc;
typedef struct
{
common com;
GimpVector4 a;
gdouble r;
} sphere;
typedef struct
{
common com;
GimpVector4 a, b, c;
} cylinder;
typedef struct
{
common com;
GimpVector4 a;
gdouble b;
} plane;
typedef struct
{
common com;
GimpVector4 color;
GimpVector4 a;
} light;
typedef struct
{
GimpVector4 v1, v2;
gshort inside;
gdouble ior;
} ray;
typedef union
{
common com;
triangle tri;
disc disc;
plane plane;
sphere sphere;
cylinder cylinder;
} object;
struct world_t
{
gint numobj;
object obj[MAXOBJECT];
gint numlight;
light light[MAXLIGHT];
gint numtexture;
texture texture[MAXTEXTURE];
gulong flags;
gshort quality;
gdouble smartambient;
gshort numatmos;
atmos atmos[MAXATMOS];
};
struct camera_t
{
GimpVector4 location, lookat, up, right;
short type;
double fov, tilt;
};
typedef struct _Designer Designer;
typedef struct _DesignerClass DesignerClass;
struct _Designer
{
GimpPlugIn parent_instance;
};
struct _DesignerClass
{
GimpPlugInClass parent_class;
};
#define DESIGNER_TYPE (designer_get_type ())
#define DESIGNER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DESIGNER_TYPE, Designer))
GType designer_get_type (void) G_GNUC_CONST;
static GList * designer_query_procedures (GimpPlugIn *plug_in);
static GimpProcedure * designer_create_procedure (GimpPlugIn *plug_in,
const gchar *name);
static GimpValueArray * designer_run (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data);
static inline void vset (GimpVector4 *v,
gdouble a,
gdouble b,
gdouble c);
static void restartrender (void);
static void drawcolor1 (GtkWidget *widget);
static void drawcolor2 (GtkWidget *widget);
static gboolean render (void);
static void realrender (GimpDrawable *drawable);
static void fileselect (GtkFileChooserAction action,
GtkWidget *parent);
static gint traceray (ray *r,
GimpVector4 *col,
gint level,
gdouble imp);
static gdouble turbulence (gdouble *point,
gdouble lofreq,
gdouble hifreq);
G_DEFINE_TYPE (Designer, designer, GIMP_TYPE_PLUG_IN)
GIMP_MAIN (DESIGNER_TYPE)
DEFINE_STD_SET_I18N
static GtkWidget *drawarea = NULL;
static guchar *img;
static gint img_stride;
static cairo_surface_t *buffer;
static guint idle_id = 0;
static sphere s;
struct textures_t
{
gint index;
gchar *s;
glong n;
};
static struct textures_t textures[] =
{
{ 0, N_("Solid"), SOLID },
{ 1, N_("Checker"), CHECKER },
{ 2, N_("Marble"), MARBLE },
{ 3, N_("Lizard"), LIZARD },
{ 4, N_("Phong"), PHONG },
{ 5, N_("Noise"), PERLIN },
{ 6, N_("Wood"), WOOD },
{ 7, N_("Spiral"), SPIRAL },
{ 8, N_("Spots"), SPOTS },
{ 0, NULL, 0 }
};
#define COLORBUTTONWIDTH 30
#define COLORBUTTONHEIGHT 20
static GtkTreeView *texturelist = NULL;
static GtkWidget *scalexscale, *scaleyscale, *scalezscale;
static GtkWidget *rotxscale, *rotyscale, *rotzscale;
static GtkWidget *posxscale, *posyscale, *poszscale;
static GtkWidget *scalescale;
static GtkWidget *turbulencescale;
static GtkWidget *amountscale;
static GtkWidget *expscale;
static GtkWidget *typemenu;
static GtkWidget *texturemenu;
#define DOT(a,b) (a[0] * b[0] + a[1] * b[1] + a[2] * b[2])
#define B 256
static gint p[B + B + 2];
static gdouble g[B + B + 2][3];
static gboolean start = TRUE;
static GRand *gr;
static void
designer_class_init (DesignerClass *klass)
{
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = designer_query_procedures;
plug_in_class->create_procedure = designer_create_procedure;
plug_in_class->set_i18n = STD_SET_I18N;
}
static void
designer_init (Designer *designer)
{
}
static GList *
designer_query_procedures (GimpPlugIn *plug_in)
{
return g_list_append (NULL, g_strdup (PLUG_IN_PROC));
}
static GimpProcedure *
designer_create_procedure (GimpPlugIn *plug_in,
const gchar *name)
{
GimpProcedure *procedure = NULL;
if (! strcmp (name, PLUG_IN_PROC))
{
procedure = gimp_image_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
designer_run, NULL, NULL);
gimp_procedure_set_image_types (procedure, "RGB*, GRAY*");
gimp_procedure_set_sensitivity_mask (procedure,
GIMP_PROCEDURE_SENSITIVE_DRAWABLE);
gimp_procedure_set_menu_label (procedure, _("Sphere _Designer..."));
gimp_procedure_add_menu_path (procedure, "<Image>/Filters/Render");
gimp_procedure_set_documentation (procedure,
_("Create an image of a textured "
"sphere"),
"This plug-in can be used to create "
"textured and/or bumpmapped spheres, "
"and uses a small lightweight "
"raytracer to perform the task with "
"good quality",
name);
gimp_procedure_set_attribution (procedure,
"Vidar Madsen",
"Vidar Madsen",
"1999");
gimp_procedure_add_bytes_aux_argument (procedure, "settings-data",
"Settings data",
"TODO: eventually we must implement proper args for every settings",
GIMP_PARAM_READWRITE);
}
return procedure;
}
static void
init (void)
{
gint i, j, k;
gdouble v[3], s;
/* Create an array of random gradient vectors uniformly on the unit sphere */
gr = g_rand_new ();
g_rand_set_seed (gr, 1); /* Use static seed, to get reproducible results */
for (i = 0; i < B; i++)
{
do
{ /* Choose uniformly in a cube */
for (j = 0; j < 3; j++)
v[j] = g_rand_double_range (gr, -1, 1);
s = DOT (v, v);
}
while (s > 1.0); /* If not in sphere try again */
s = sqrt (s);
for (j = 0; j < 3; j++) /* Else normalize */
g[i][j] = v[j] / s;
}
/* Create a pseudorandom permutation of [1..B] */
for (i = 0; i < B; i++)
p[i] = i;
for (i = B; i > 0; i -= 2)
{
k = p[i];
p[i] = p[j = g_rand_int_range (gr, 0, B)];
p[j] = k;
}
/* Extend g and p arrays to allow for faster indexing */
for (i = 0; i < B + 2; i++)
{
p[B + i] = p[i];
for (j = 0; j < 3; j++)
g[B + i][j] = g[i][j];
}
g_rand_free (gr);
}
#define setup(i,b0,b1,r0,r1) \
t = vec[i] + 10000.; \
b0 = ((int)t) & (B-1); \
b1 = (b0+1) & (B-1); \
r0 = t - (int)t; \
r1 = r0 - 1.;
static gdouble
noise3 (gdouble * vec)
{
gint bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11;
gdouble rx0, rx1, ry0, ry1, rz0, rz1, *q, sx, sy, sz, a, b, c, d, t, u, v;
gint i, j;
if (start)
{
start = FALSE;
init ();
}
setup (0, bx0, bx1, rx0, rx1);
setup (1, by0, by1, ry0, ry1);
setup (2, bz0, bz1, rz0, rz1);
i = p[bx0];
j = p[bx1];
b00 = p[i + by0];
b10 = p[j + by0];
b01 = p[i + by1];
b11 = p[j + by1];
#define at(rx,ry,rz) ( rx * q[0] + ry * q[1] + rz * q[2] )
#define surve(t) ( t * t * (3. - 2. * t) )
#define lerp(t, a, b) ( a + t * (b - a) )
sx = surve (rx0);
sy = surve (ry0);
sz = surve (rz0);
q = g[b00 + bz0];
u = at (rx0, ry0, rz0);
q = g[b10 + bz0];
v = at (rx1, ry0, rz0);
a = lerp (sx, u, v);
q = g[b01 + bz0];
u = at (rx0, ry1, rz0);
q = g[b11 + bz0];
v = at (rx1, ry1, rz0);
b = lerp (sx, u, v);
c = lerp (sy, a, b); /* interpolate in y at lo x */
q = g[b00 + bz1];
u = at (rx0, ry0, rz1);
q = g[b10 + bz1];
v = at (rx1, ry0, rz1);
a = lerp (sx, u, v);
q = g[b01 + bz1];
u = at (rx0, ry1, rz1);
q = g[b11 + bz1];
v = at (rx1, ry1, rz1);
b = lerp (sx, u, v);
d = lerp (sy, a, b); /* interpolate in y at hi x */
return 1.5 * lerp (sz, c, d); /* interpolate in z */
}
static double
turbulence (gdouble * point, gdouble lofreq, gdouble hifreq)
{
gdouble freq, t, p[3];
p[0] = point[0] + 123.456;
p[1] = point[1] + 234.567;
p[2] = point[2] + 345.678;
t = 0;
for (freq = lofreq; freq < hifreq; freq *= 2.)
{
t += noise3 (p) / freq;
p[0] *= 2.;
p[1] *= 2.;
p[2] *= 2.;
}
return t - 0.3; /* readjust to make mean value = 0.0 */
}
static struct world_t world;
static inline void
vcopy (GimpVector4 *a, GimpVector4 *b)
{
*a = *b;
}
static inline void
vcross (GimpVector4 *r, GimpVector4 *a, GimpVector4 *b)
{
r->x = a->y * b->z - a->z * b->y;
r->y = -(a->x * b->z - a->z * b->x);
r->z = a->x * b->y - a->y * b->x;
}
static inline gdouble
vdot (GimpVector4 *a, GimpVector4 *b)
{
return a->x * b->x + a->y * b->y + a->z * b->z;
}
static inline gdouble
vdist (GimpVector4 *a, GimpVector4 *b)
{
gdouble x, y, z;
x = a->x - b->x;
y = a->y - b->y;
z = a->z - b->z;
return sqrt (x * x + y * y + z * z);
}
static inline gdouble
vdist2 (GimpVector4 *a, GimpVector4 *b)
{
gdouble x, y, z;
x = a->x - b->x;
y = a->y - b->y;
z = a->z - b->z;
return x * x + y * y + z * z;
}
static inline gdouble
vlen (GimpVector4 *a)
{
return sqrt (a->x * a->x + a->y * a->y + a->z * a->z);
}
static inline void
vnorm (GimpVector4 *a, gdouble v)
{
gdouble d;
d = vlen (a);
a->x *= v / d;
a->y *= v / d;
a->z *= v / d;
}
static inline void
vrotate (GimpVector4 *axis, gdouble ang, GimpVector4 *vector)
{
gdouble rad = ang / 180.0 * G_PI;
gdouble ax = vector->x;
gdouble ay = vector->y;
gdouble az = vector->z;
gdouble x = axis->x;
gdouble y = axis->y;
gdouble z = axis->z;
gdouble c = cos (rad);
gdouble s = sin (rad);
gdouble c1 = 1.0 - c;
gdouble xx = c1 * x * x;
gdouble yy = c1 * y * y;
gdouble zz = c1 * z * z;
gdouble xy = c1 * x * y;
gdouble xz = c1 * x * z;
gdouble yz = c1 * y * z;
gdouble sx = s * x;
gdouble sy = s * y;
gdouble sz = s * z;
vector->x = (xx + c) * ax + (xy + sz) * ay + (xz - sy) * az;
vector->y = (xy - sz) * ax + (yy + c) * ay + (yz + sx) * az;
vector->z = (xz + sy) * ax + (yz - sx) * ay + (zz + c) * az;
}
static inline void
vset (GimpVector4 *v, gdouble a, gdouble b, gdouble c)
{
v->x = a;
v->y = b;
v->z = c;
v->w = 1.0;
}
static inline void
vcset (GimpVector4 *v, gdouble a, gdouble b, gdouble c, gdouble d)
{
v->x = a;
v->y = b;
v->z = c;
v->w = d;
}
static inline void
vvrotate (GimpVector4 *p, GimpVector4 *rot)
{
GimpVector4 axis;
if (rot->x != 0.0)
{
vset (&axis, 1, 0, 0);
vrotate (&axis, rot->x, p);
}
if (rot->y != 0.0)
{
vset (&axis, 0, 1, 0);
vrotate (&axis, rot->y, p);
}
if (rot->z != 0.0)
{
vset (&axis, 0, 0, 1);
vrotate (&axis, rot->z, p);
}
}
static inline void
vsub (GimpVector4 *a, GimpVector4 *b)
{
a->x -= b->x;
a->y -= b->y;
a->z -= b->z;
a->w -= b->w;
}
static inline void
vadd (GimpVector4 *a, GimpVector4 *b)
{
a->x += b->x;
a->y += b->y;
a->z += b->z;
a->w += b->w;
}
static inline void
vneg (GimpVector4 *a)
{
a->x = -a->x;
a->y = -a->y;
a->z = -a->z;
a->w = -a->w;
}
static inline void
vmul (GimpVector4 *v, gdouble a)
{
v->x *= a;
v->y *= a;
v->z *= a;
v->w *= a;
}
static inline void
vvmul (GimpVector4 *a, GimpVector4 *b)
{
a->x *= b->x;
a->y *= b->y;
a->z *= b->z;
a->w *= b->w;
}
static inline void
vvdiv (GimpVector4 *a, GimpVector4 *b)
{
a->x /= b->x;
a->y /= b->y;
a->z /= b->z;
}
static void
vmix (GimpVector4 *r, GimpVector4 *a, GimpVector4 *b, gdouble v)
{
gdouble i = 1.0 - v;
r->x = a->x * v + b->x * i;
r->y = a->y * v + b->y * i;
r->z = a->z * v + b->z * i;
r->w = a->w * v + b->w * i;
}
static double
vmax (GimpVector4 *a)
{
gdouble max = fabs (a->x);
if (fabs (a->y) > max)
max = fabs (a->y);
if (fabs (a->z) > max)
max = fabs (a->z);
if (fabs (a->w) > max)
max = fabs (a->w);
return max;
}
#if 0
static void
vavg (GimpVector4 * a)
{
gdouble s;
s = (a->x + a->y + a->z) / 3.0;
a->x = a->y = a->z = s;
}
#endif
static void
trianglenormal (GimpVector4 * n, gdouble *t, triangle * tri)
{
triangle tmp;
vcopy (&tmp.b, &tri->b);
vcopy (&tmp.c, &tri->c);
vsub (&tmp.b, &tri->a);
vsub (&tmp.c, &tri->a);
vset (&tmp.a, 0, 0, 0);
vcross (n, &tmp.b, &tmp.c);
if (t)
*t = vdot (&tmp.b, &tmp.c);
}
static gdouble
checkdisc (ray * r, disc * disc)
{
GimpVector4 p, *v = &disc->a;
gdouble t, d2;
gdouble i, j, k;
i = r->v2.x - r->v1.x;
j = r->v2.y - r->v1.y;
k = r->v2.z - r->v1.z;
t = -(v->x * r->v1.x + v->y * r->v1.y + v->z * r->v1.z - disc->b) /
(v->x * i + v->y * j + v->z * k);
p.x = r->v1.x + i * t;
p.y = r->v1.y + j * t;
p.z = r->v1.z + k * t;
d2 = vdist2 (&p, v);
if (d2 > disc->r * disc->r)
t = 0.0;
return t;
}
static gdouble
checksphere (ray * r, sphere * sphere)
{
GimpVector4 cendir, rdir;
gdouble dirproj, cdlensq;
gdouble linear, constant, rsq, quadratic, discriminant;
gdouble smallzero, solmin, solmax, tolerance = 0.001;
vcopy (&rdir, &r->v2);
vsub (&rdir, &r->v1);
rsq = sphere->r * sphere->r;
vcopy (&cendir, &r->v1);
vsub (&cendir, &sphere->a);
dirproj = vdot (&rdir, &cendir);
cdlensq = vdot (&cendir, &cendir);
if ((cdlensq >= rsq) && (dirproj > 0.0))
return 0.0;
linear = 2.0 * dirproj;
constant = cdlensq - rsq;
quadratic = vdot (&rdir, &rdir);
smallzero = (constant / linear);
if ((smallzero < tolerance) && (smallzero > -tolerance))
{
solmin = -linear / quadratic;
if (solmin > tolerance)
{
return solmin;
/*
*hits = solmin;
return 1;
*/
}
else
return 0.0;
}
discriminant = linear * linear - 4.0 * quadratic * constant;
if (discriminant < 0.0)
return 0.0;
quadratic *= 2.0;
discriminant = sqrt (discriminant);
solmax = (-linear + discriminant) / (quadratic);
solmin = (-linear - discriminant) / (quadratic);
if (solmax < tolerance)
return 0.0;
if (solmin < tolerance)
{
return solmax;
/*
* hits = solmax;
* return 1;
*/
}
else
{
return solmin;
/*
* hits++ = solmin;
* hits = solmax;
* return 2;
*/
}
}
static gdouble
checkcylinder (ray * r, cylinder * cylinder)
{
/* FIXME */
return 0.0;
}
static gdouble
checkplane (ray * r, plane * plane)
{
GimpVector4 *v = &plane->a;
gdouble t;
gdouble i, j, k;
i = r->v2.x - r->v1.x;
j = r->v2.y - r->v1.y;
k = r->v2.z - r->v1.z;
t = -(v->x * r->v1.x + v->y * r->v1.y + v->z * r->v1.z - plane->b) /
(v->x * i + v->y * j + v->z * k);
return t;
}
static gdouble
checktri (ray * r, triangle * tri)
{
GimpVector4 ed1, ed2;
GimpVector4 tvec, pvec, qvec;
gdouble det, idet, t, u, v;
GimpVector4 *orig, dir;
orig = &r->v1;
dir = r->v2;
vsub (&dir, orig);
ed1.x = tri->c.x - tri->a.x;
ed1.y = tri->c.y - tri->a.y;
ed1.z = tri->c.z - tri->a.z;
ed2.x = tri->b.x - tri->a.x;
ed2.y = tri->b.y - tri->a.y;
ed2.z = tri->b.z - tri->a.z;
vcross (&pvec, &dir, &ed2);
det = vdot (&ed1, &pvec);
idet = 1.0 / det;
tvec.x = orig->x;
tvec.y = orig->y;
tvec.z = orig->z;
vsub (&tvec, &tri->a);
u = vdot (&tvec, &pvec) * idet;
if (u < 0.0)
return 0;
if (u > 1.0)
return 0;
vcross (&qvec, &tvec, &ed1);
v = vdot (&dir, &qvec) * idet;
if ((v < 0.0) || (u + v > 1.0))
return 0;
t = vdot (&ed2, &qvec) * idet;
return t;
}
static void
transformpoint (GimpVector4 * p, texture * t)
{
gdouble point[3], f;
if ((t->rotate.x != 0.0) || (t->rotate.y != 0.0) || (t->rotate.z != 0.0))
vvrotate (p, &t->rotate);
vvdiv (p, &t->scale);
vsub (p, &t->translate);
if ((t->turbulence.x != 0.0) || (t->turbulence.y != 0.0) ||
(t->turbulence.z != 0.0))
{
point[0] = p->x;
point[1] = p->y;
point[2] = p->z;
f = turbulence (point, 1, 256);
p->x += t->turbulence.x * f;
p->y += t->turbulence.y * f;
p->z += t->turbulence.z * f;
}
}
static void
checker (GimpVector4 *q, GimpVector4 *col, texture *t)
{
gint c = 0;
GimpVector4 p;
p = *q;
transformpoint (&p, t);
vmul (&p, 0.25);
p.x += 0.00001;
p.y += 0.00001;
p.z += 0.00001;
if (p.x < 0.0)
p.x = 0.5 - p.x;
if (p.y < 0.0)
p.y = 0.5 - p.y;
if (p.z < 0.0)
p.z = 0.5 - p.z;
if ((p.x - (gint) p.x) < 0.5)
c ^= 1;
if ((p.y - (gint) p.y) < 0.5)
c ^= 1;
if ((p.z - (gint) p.z) < 0.5)
c ^= 1;
*col = (c) ? t->color1 : t->color2;
}
static void
gradcolor (GimpVector4 *col, gradient *t, gdouble val)
{
gint i;
gdouble d;
GimpVector4 tmpcol;
val = CLAMP (val, 0.0, 1.0);
for (i = 0; i < t->numcol; i++)
{
if (t->pos[i] == val)
{
*col = t->color[i];
return;
}
if (t->pos[i] > val)
{
d = (val - t->pos[i - 1]) / (t->pos[i] - t->pos[i - 1]);
vcopy (&tmpcol, &t->color[i]);
vmul (&tmpcol, d);
vcopy (col, &tmpcol);
vcopy (&tmpcol, &t->color[i - 1]);
vmul (&tmpcol, 1.0 - d);
vadd (col, &tmpcol);
return;
}
}
g_printerr ("Error in gradient!\n");
vset (col, 0, 1, 0);
}
static void
marble (GimpVector4 *q, GimpVector4 *col, texture *t)
{
gdouble f;
GimpVector4 p;
p = *q;
transformpoint (&p, t);
f = sin (p.x * 4) / 2 + 0.5;
f = pow (f, t->exp);
if (t->flags & GRADIENT)
gradcolor (col, &t->gradient, f);
else
vmix (col, &t->color1, &t->color2, f);
}
static void
lizard (GimpVector4 *q, GimpVector4 *col, texture *t)
{
gdouble f;
GimpVector4 p;
p = *q;
transformpoint (&p, t);
f = fabs (sin (p.x * 4));
f += fabs (sin (p.y * 4));
f += fabs (sin (p.z * 4));
f /= 3.0;
f = pow (f, t->exp);
if (t->flags & GRADIENT)
gradcolor (col, &t->gradient, f);
else
vmix (col, &t->color1, &t->color2, f);
}
static void
wood (GimpVector4 *q, GimpVector4 *col, texture *t)
{
gdouble f;
GimpVector4 p;
p = *q;
transformpoint (&p, t);
f = fabs (p.x);
f = f - (int) f;
f = pow (f, t->exp);
if (t->flags & GRADIENT)
gradcolor (col, &t->gradient, f);
else
vmix (col, &t->color1, &t->color2, f);
}
static void
spiral (GimpVector4 *q, GimpVector4 *col, texture *t)
{
gdouble f;
GimpVector4 p;
p = *q;
transformpoint (&p, t);
f = fabs (atan2 (p.x, p.z) / G_PI / 2 + p.y + 99999);
f = f - (int) f;
f = pow (f, t->exp);
if (t->flags & GRADIENT)
gradcolor (col, &t->gradient, f);
else
vmix (col, &t->color1, &t->color2, f);
}
static void
spots (GimpVector4 *q, GimpVector4 *col, texture *t)
{
gdouble f;
GimpVector4 p, r;
p = *q;
transformpoint (&p, t);
p.x += 10000.0;
p.y += 10000.0;
p.z += 10000.0;
vset (&r, (gint) (p.x + 0.5), (gint) (p.y + 0.5), (gint) (p.z + 0.5));
f = vdist (&p, &r);
f = cos (f * G_PI);
f = CLAMP (f, 0.0, 1.0);
f = pow (f, t->exp);
if (t->flags & GRADIENT)
gradcolor (col, &t->gradient, f);
else
vmix (col, &t->color1, &t->color2, f);
}
static void
perlin (GimpVector4 * q, GimpVector4 * col, texture * t)
{
gdouble f, point[3];
GimpVector4 p;
p = *q;
transformpoint (&p, t);
point[0] = p.x;
point[1] = p.y;
point[2] = p.z;
f = turbulence (point, 1, 256) * 0.3 + 0.5;
f = pow (f, t->exp);
if (t->flags & GRADIENT)
gradcolor (col, &t->gradient, f);
else
vmix (col, &t->color1, &t->color2, f);
}
static void
imagepixel (GimpVector4 * q, GimpVector4 * col, texture * t)
{
GimpVector4 p;
gint x, y;
guchar *rgb;
p = *q;
transformpoint (&p, t);
x = (p.x * t->image.xsize);
y = (p.y * t->image.ysize);
x = (x % t->image.xsize + t->image.xsize) % t->image.xsize;
y = (y % t->image.ysize + t->image.ysize) % t->image.ysize;
rgb = &t->image.rgb[x * 3 + (t->image.ysize - 1 - y) * t->image.xsize * 3];
vset (col, rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0);
}
static void
objcolor (GimpVector4 *col, GimpVector4 *p, common *obj)
{
gint i;
texture *t;
GimpVector4 tmpcol;
vcset (col, 0, 0, 0, 0);
for (i = 0; i < obj->numtexture; i++)
{
t = &obj->texture[i];
if (world.quality < 1)
{
vadd (col, &t->color1);
continue;
}
vset (&tmpcol, 0, 0, 0);
switch (t->type)
{
case SOLID:
vcopy (&tmpcol, &t->color1);
break;
case CHECKER:
checker (p, &tmpcol, t);
break;
case MARBLE:
marble (p, &tmpcol, t);
break;
case LIZARD:
lizard (p, &tmpcol, t);
break;
case PERLIN:
perlin (p, &tmpcol, t);
break;
case WOOD:
wood (p, &tmpcol, t);
break;
case SPIRAL:
spiral (p, &tmpcol, t);
break;
case SPOTS:
spots (p, &tmpcol, t);
break;
case IMAGE:
imagepixel (p, &tmpcol, t);
break;
case PHONG:
case REFRACTION:
case REFLECTION:
case TRANSPARENT:
case SMOKE:
/* Silently ignore non-color textures */
continue;
break;
default:
g_printerr ("Warning: unknown texture %d\n", t->type);
break;
}
vmul (&tmpcol, t->amount);
vadd (col, &tmpcol);
}
if (!i)
{
g_printerr ("Warning: object %p has no textures\n", obj);
}
}
static void
objnormal (GimpVector4 *res, common *obj, GimpVector4 *p)
{
gint i;
switch (obj->type)
{
case TRIANGLE:
trianglenormal (res, NULL, (triangle *) obj);
break;
case DISC:
vcopy (res, &((disc *) obj)->a);
break;
case PLANE:
vcopy (res, &((plane *) obj)->a);
break;
case SPHERE:
vcopy (res, &((sphere *) obj)->a);
vsub (res, p);
break;
case CYLINDER:
vset (res, 1, 1, 1); /* fixme */
break;
default:
g_error ("objnormal(): Unsupported object type!?\n");
}
vnorm (res, 1.0);
for (i = 0; i < obj->numnormal; i++)
{
gint k;
GimpVector4 tmpcol[6];
GimpVector4 q[6], nres;
texture *t = &obj->normal[i];
gdouble nstep = 0.1;
vset (&nres, 0, 0, 0);
for (k = 0; k < 6; k++)
{
vcopy (&q[k], p);
}
q[0].x += nstep;
q[1].x -= nstep;
q[2].y += nstep;
q[3].y -= nstep;
q[4].z += nstep;
q[5].z -= nstep;
switch (t->type)
{
case MARBLE:
for (k = 0; k < 6; k++)
marble (&q[k], &tmpcol[k], t);
break;
case LIZARD:
for (k = 0; k < 6; k++)
lizard (&q[k], &tmpcol[k], t);
break;
case PERLIN:
for (k = 0; k < 6; k++)
perlin (&q[k], &tmpcol[k], t);
break;
case WOOD:
for (k = 0; k < 6; k++)
wood (&q[k], &tmpcol[k], t);
break;
case SPIRAL:
for (k = 0; k < 6; k++)
spiral (&q[k], &tmpcol[k], t);
break;
case SPOTS:
for (k = 0; k < 6; k++)
spots (&q[k], &tmpcol[k], t);
break;
case IMAGE:
for (k = 0; k < 6; k++)
imagepixel (&q[k], &tmpcol[k], t);
break;
case CHECKER:
case SOLID:
case PHONG:
case REFRACTION:
case REFLECTION:
case TRANSPARENT:
case SMOKE:
continue;
break;
default:
g_printerr ("Warning: unknown texture %d\n", t->type);
break;
}
nres.x = tmpcol[0].x - tmpcol[1].x;
nres.y = tmpcol[2].x - tmpcol[3].x;
nres.z = tmpcol[4].x - tmpcol[5].x;
vadd (&nres, res);
vnorm (&nres, 1.0);
vmul (&nres, t->amount);
vadd (res, &nres);
vnorm (res, 1.0);
}
}
/*
Quality:
0 = Color only
1 = Textures
2 = Light + Normals
3 = Shadows
4 = Phong
5 = Reflection + Refraction
*/
static void
calclight (GimpVector4 * col, GimpVector4 * point, common * obj)
{
gint i, j;
ray r;
gdouble b, a;
GimpVector4 lcol;
GimpVector4 norm;
GimpVector4 pcol;
vcset (col, 0, 0, 0, 0);
objcolor (&pcol, point, obj);
a = pcol.w;
if (world.quality < 2)
{
vcopy (col, &pcol);
return;
}
for (i = 0; i < obj->numtexture; i++)
{
if (obj->texture[i].type == PHONG)
continue;
if (obj->texture[i].type == REFLECTION)
continue;
if (obj->texture[i].type == REFRACTION)
continue;
if (obj->texture[i].type == TRANSPARENT)
continue;
if (obj->texture[i].type == SMOKE)
continue;
vcopy (&lcol, &pcol);
vvmul (&lcol, &obj->texture[i].ambient);
vadd (col, &lcol);
}
objnormal (&norm, obj, point);
vnorm (&norm, 1.0);
r.inside = -1;
r.ior = 1.0;
for (i = 0; i < world.numlight; i++)
{
vcopy (&r.v1, point);
vcopy (&r.v2, &world.light[i].a);
vmix (&r.v1, &r.v1, &r.v2, 0.9999);
vsub (&r.v1, &r.v2);
vnorm (&r.v1, 1.0);
b = vdot (&r.v1, &norm);
if (b < 0.0)
continue;
for (j = 0; j < obj->numtexture; j++)
{
if (obj->texture[j].type == PHONG)
continue;
if (obj->texture[j].type == REFLECTION)
continue;
if (obj->texture[j].type == REFRACTION)
continue;
if (obj->texture[j].type == TRANSPARENT)
continue;
if (obj->texture[j].type == SMOKE)
continue;
vcopy (&lcol, &pcol);
vvmul (&lcol, &world.light[i].color);
vvmul (&lcol, &obj->texture[j].diffuse);
vmul (&lcol, b);
vadd (col, &lcol);
}
}
col->w = a;
}
static void
calcphong (common * obj, ray * r2, GimpVector4 * col)
{
gint i, j;
ray r;
gdouble b;
GimpVector4 lcol;
GimpVector4 norm;
GimpVector4 pcol;
gdouble ps;
vcopy (&pcol, col);
vcopy (&norm, &r2->v2);
vsub (&norm, &r2->v1);
vnorm (&norm, 1.0);
r.inside = -1;
r.ior = 1.0;
for (i = 0; i < world.numlight; i++)
{
vcopy (&r.v1, &r2->v1);
vcopy (&r.v2, &world.light[i].a);
vmix (&r.v1, &r.v1, &r.v2, 0.9999);
if (traceray (&r, NULL, -1, 1.0))
continue;
/* OK, light is visible */
vsub (&r.v1, &r.v2);
vnorm (&r.v1, 1.0);
b = -vdot (&r.v1, &norm);
for (j = 0; j < obj->numtexture; j++)
{
if (obj->texture[j].type != PHONG)
continue;
ps = obj->texture[j].phongsize;
if (b < (1 - ps))
continue;
ps = (b - (1 - ps)) / ps;
vcopy (&lcol, &obj->texture[j].phongcolor);
vvmul (&lcol, &world.light[i].color);
vmul (&lcol, ps);
vadd (col, &lcol);
}
}
}
static int
traceray (ray * r, GimpVector4 * col, gint level, gdouble imp)
{
gint i, b = -1;
gdouble t = -1.0, min = 0.0;
common *obj, *bobj = NULL;
gint hits = 0;
GimpVector4 p = { 0, 0, 0, 0 };
if ((level == 0) || (imp < 0.005))
{
vset (col, 0, 1, 0);
return 0;
}
for (i = 0; i < world.numobj; i++)
{
obj = (common *) & world.obj[i];
switch (obj->type)
{
case TRIANGLE:
t = checktri (r, (triangle *) & world.obj[i]);
break;
case DISC:
t = checkdisc (r, (disc *) & world.obj[i]);
break;
case PLANE:
t = checkplane (r, (plane *) & world.obj[i]);
break;
case SPHERE:
t = checksphere (r, (sphere *) & world.obj[i]);
break;
case CYLINDER:
t = checkcylinder (r, (cylinder *) & world.obj[i]);
break;
default:
g_error ("Illegal object!!\n");
}
if (t <= 0.0)
continue;
if (!(obj->flags & NOSHADOW) && (level == -1))
{
return i + 1;
}
hits++;
if ((!bobj) || (t < min))
{
min = t;
b = i;
bobj = obj;
}
}
if (level == -1)
return 0;
if (bobj)
{
p.x = r->v1.x + (r->v2.x - r->v1.x) * min;
p.y = r->v1.y + (r->v2.y - r->v1.y) * min;
p.z = r->v1.z + (r->v2.z - r->v1.z) * min;
calclight (col, &p, bobj);
if (world.flags & SMARTAMBIENT)
{
gdouble ambient = 0.3 * exp (-min / world.smartambient);
GimpVector4 lcol;
objcolor (&lcol, &p, bobj);
vmul (&lcol, ambient);
vadd (col, &lcol);
}
for (i = 0; i < bobj->numtexture; i++)
{
if ((world.quality >= 4)
&& ((bobj->texture[i].type == REFLECTION)
|| (bobj->texture[i].type == PHONG)))
{
GimpVector4 refcol, norm, ocol;
ray ref;
objcolor (&ocol, &p, bobj);
vcopy (&ref.v1, &p);
vcopy (&ref.v2, &r->v1);
ref.inside = r->inside;
ref.ior = r->ior;
vmix (&ref.v1, &ref.v1, &ref.v2, 0.9999); /* push it a tad */
vsub (&ref.v2, &p);
objnormal (&norm, bobj, &p);
vnorm (&norm, 1.0);
vrotate (&norm, 180.0, &ref.v2);
vmul (&norm, -0.0001); /* push it a tad */
vadd (&ref.v1, &norm);
vnorm (&ref.v2, 1.0);
vadd (&ref.v2, &p);
if ((world.quality >= 5)
&& (bobj->texture[i].type == REFLECTION))
{
traceray (&ref, &refcol, level - 1,
imp * vmax (&bobj->texture[i].reflection));
vvmul (&refcol, &bobj->texture[i].reflection);
refcol.w = ocol.w;
vadd (col, &refcol);
}
if (bobj->texture[i].type == PHONG)
{
vcset (&refcol, 0, 0, 0, 0);
calcphong (bobj, &ref, &refcol);
refcol.w = ocol.w;
vadd (col, &refcol);
}
}
if ((world.quality >= 5) && (col->w < 1.0))
{
GimpVector4 refcol;
ray ref;
vcopy (&ref.v1, &p);
vcopy (&ref.v2, &p);
vsub (&ref.v2, &r->v1);
vnorm (&ref.v2, 1.0);
vadd (&ref.v2, &p);
vmix (&ref.v1, &ref.v1, &ref.v2, 0.999); /* push it a tad */
traceray (&ref, &refcol, level - 1, imp * (1.0 - col->w));
vmul (&refcol, (1.0 - col->w));
vadd (col, &refcol);
}
if ((world.quality >= 5) && (bobj->texture[i].type == TRANSPARENT))
{
GimpVector4 refcol;
ray ref;
vcopy (&ref.v1, &p);
vcopy (&ref.v2, &p);
vsub (&ref.v2, &r->v1);
vnorm (&ref.v2, 1.0);
vadd (&ref.v2, &p);
vmix (&ref.v1, &ref.v1, &ref.v2, 0.999); /* push it a tad */
traceray (&ref, &refcol, level - 1,
imp * vmax (&bobj->texture[i].transparent));
vvmul (&refcol, &bobj->texture[i].transparent);
vadd (col, &refcol);
}
if ((world.quality >= 5) && (bobj->texture[i].type == SMOKE))
{
GimpVector4 smcol, raydir, norm;
double tran;
ray ref;
vcopy (&ref.v1, &p);
vcopy (&ref.v2, &p);
vsub (&ref.v2, &r->v1);
vnorm (&ref.v2, 1.0);
vadd (&ref.v2, &p);
objnormal (&norm, bobj, &p);
vcopy (&raydir, &r->v2);
vsub (&raydir, &r->v1);
vnorm (&raydir, 1.0);
tran = vdot (&norm, &raydir);
if (tran < 0.0)
continue;
tran *= tran;
vcopy (&smcol, &bobj->texture[i].color1);
vmul (&smcol, tran);
vadd (col, &smcol);
}
if ((world.quality >= 5) && (bobj->texture[i].type == REFRACTION))
{
GimpVector4 refcol, norm, tmpv;
ray ref;
double c1, c2, n1, n2, n;
vcopy (&ref.v1, &p);
vcopy (&ref.v2, &p);
vsub (&ref.v2, &r->v1);
vadd (&ref.v2, &r->v2);
vmix (&ref.v1, &ref.v1, &ref.v2, 0.999); /* push it a tad */
vsub (&ref.v2, &p);
objnormal (&norm, bobj, &p);
if (r->inside == b)
{
ref.inside = -1;
ref.ior = 1.0;
}
else
{
ref.inside = b;
ref.ior = bobj->texture[i].ior;
}
c1 = vdot (&norm, &ref.v2);
if (ref.inside < 0)
c1 = -c1;
n1 = r->ior; /* IOR of current media */
n2 = ref.ior; /* IOR of new media */
n = n1 / n2;
c2 = 1.0 - n * n * (1.0 - c1 * c1);
if (c2 < 0.0)
{
/* FIXME: Internal reflection should occur */
c2 = sqrt (-c2);
}
else
{
c2 = sqrt (c2);
}
vmul (&ref.v2, n);
vcopy (&tmpv, &norm);
vmul (&tmpv, n * c1 - c2);
vadd (&ref.v2, &tmpv);
vnorm (&ref.v2, 1.0);
vadd (&ref.v2, &p);
traceray (&ref, &refcol, level - 1,
imp * vmax (&bobj->texture[i].refraction));
vvmul (&refcol, &bobj->texture[i].refraction);
vadd (col, &refcol);
}
}
}
else
{
vcset (col, 0, 0, 0, 0);
min = 10000.0;
vcset (&p, 0, 0, 0, 0);
}
for (i = 0; i < world.numatmos; i++)
{
GimpVector4 tmpcol;
if (world.atmos[i].type == FOG)
{
gdouble v, pt[3];
pt[0] = p.x;
pt[1] = p.y;
pt[2] = p.z;
if ((v = world.atmos[i].turbulence) > 0.0)
v = turbulence (pt, 1, 256) * world.atmos[i].turbulence;
v = exp (-(min + v) / world.atmos[i].density);
vmul (col, v);
vcopy (&tmpcol, &world.atmos[i].color);
vmul (&tmpcol, 1.0 - v);
vadd (col, &tmpcol);
}
}
return hits;
}
static void
setdefaults (texture * t)
{
memset (t, 0, sizeof (texture));
t->type = SOLID;
vcset (&t->color1, 1, 1, 1, 1);
vcset (&t->color2, 0, 0, 0, 1);
vcset (&t->diffuse, 1, 1, 1, 1);
vcset (&t->ambient, 0, 0, 0, 1);
vset (&t->scale, 1, 1, 1);
vset (&t->rotate, 0, 0, 0);
vset (&t->translate, 0, 0, 0);
t->oscale = 1.0;
t->amount = 1.0;
t->exp = 1.0;
}
static gchar *
mklabel (texture * t)
{
struct textures_t *l;
static gchar tmps[100];
if (t->majtype == 0)
g_strlcpy (tmps, _("Texture"), sizeof(tmps));
else if (t->majtype == 1)
g_strlcpy (tmps, _("Bumpmap"), sizeof(tmps));
else if (t->majtype == 2)
g_strlcpy (tmps, _("Light"), sizeof(tmps));
else
g_strlcpy (tmps, "<unknown>", sizeof(tmps));
if ((t->majtype == 0) || (t->majtype == 1))
{
g_strlcat (tmps, " / ", sizeof(tmps));
l = textures;
while (l->s)
{
if (t->type == l->n)
{
g_strlcat (tmps, gettext (l->s), sizeof(tmps));
break;
}
l++;
}
}
return tmps;
}
static texture *
currenttexture (void)
{
GtkTreeSelection *sel;
GtkTreeIter iter;
texture *t = NULL;
sel = gtk_tree_view_get_selection (texturelist);
if (gtk_tree_selection_get_selected (sel, NULL, &iter))
{
gtk_tree_model_get (gtk_tree_view_get_model (texturelist), &iter,
TEXTURE, &t,
-1);
}
return t;
}
static void
relabel (void)
{
GtkTreeModel *model;
GtkTreeSelection *sel;
GtkTreeIter iter;
texture *t = NULL;
sel = gtk_tree_view_get_selection (texturelist);
if (gtk_tree_selection_get_selected (sel, NULL, &iter))
{
model = gtk_tree_view_get_model (texturelist);
gtk_tree_model_get (model, &iter,
TEXTURE, &t,
-1);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
TYPE, mklabel (t),
-1);
}
}
static gboolean noupdate = FALSE;
static void
setvals (texture *t)
{
struct textures_t *l;
if (!t)
return;
noupdate = TRUE;
gtk_spin_button_set_value (GTK_SPIN_BUTTON (amountscale), t->amount);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (scalescale), t->oscale);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (scalexscale), t->scale.x);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (scaleyscale), t->scale.y);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (scalezscale), t->scale.z);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (rotxscale), t->rotate.x);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (rotyscale), t->rotate.y);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (rotzscale), t->rotate.z);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (posxscale), t->translate.x);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (posyscale), t->translate.y);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (poszscale), t->translate.z);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (turbulencescale), t->turbulence.x);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (expscale), t->exp);
drawcolor1 (NULL);
drawcolor2 (NULL);
l = textures;
while (l->s)
{
if (l->n == t->type)
{
gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (texturemenu),
l->index);
break;
}
l++;
}
gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (typemenu), t->majtype);
noupdate = FALSE;
}
static void
selectitem (GtkTreeSelection *treeselection,
gpointer data)
{
setvals (currenttexture ());
}
static void
addtexture (void)
{
GtkListStore *list_store;
GtkTreeIter iter;
gint n = s.com.numtexture;
if (n == MAXTEXTUREPEROBJ - 1)
return;
setdefaults (&s.com.texture[n]);
list_store = GTK_LIST_STORE (gtk_tree_view_get_model (texturelist));
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (list_store, &iter,
TYPE, mklabel (&s.com.texture[n]),
TEXTURE, &s.com.texture[n],
-1);
gtk_tree_selection_select_iter (gtk_tree_view_get_selection (texturelist),
&iter);
s.com.numtexture++;
restartrender ();
}
static void
duptexture (void)
{
GtkListStore *list_store;
GtkTreeIter iter;
texture *t = currenttexture ();
gint n = s.com.numtexture;
if (n == MAXTEXTUREPEROBJ - 1)
return;
if (!t)
return;
s.com.texture[n] = *t;
list_store = GTK_LIST_STORE (gtk_tree_view_get_model (texturelist));
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (list_store, &iter,
TYPE, mklabel (&s.com.texture[n]),
TEXTURE, &s.com.texture[n],
-1);
gtk_tree_selection_select_iter (gtk_tree_view_get_selection (texturelist),
&iter);
s.com.numtexture++;
restartrender ();
}
static void
rebuildlist (void)
{
GtkListStore *list_store;
GtkTreeSelection *sel;
GtkTreeIter iter;
gint n;
sel = gtk_tree_view_get_selection (texturelist);
for (n = 0; n < s.com.numtexture; n++)
{
if (s.com.numtexture && (s.com.texture[n].majtype < 0))
{
gint i;
for (i = n; i < s.com.numtexture - 1; i++)
s.com.texture[i] = s.com.texture[i + 1];
s.com.numtexture--;
n--;
}
}
list_store = GTK_LIST_STORE (gtk_tree_view_get_model (texturelist));
for (n = 0; n < s.com.numtexture; n++)
{
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (list_store, &iter,
TYPE, mklabel (&s.com.texture[n]),
TEXTURE, &s.com.texture[n],
-1);
}
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter))
gtk_tree_selection_select_iter (sel, &iter);
restartrender ();
}
static void
deltexture (void)
{
GtkTreeSelection *sel;
GtkTreeModel *model;
GtkTreeIter iter;
texture *t = NULL;
sel = gtk_tree_view_get_selection (texturelist);
if (gtk_tree_selection_get_selected (sel, NULL, &iter))
{
model = gtk_tree_view_get_model (texturelist);
gtk_tree_model_get (model, &iter,
TEXTURE, &t,
-1);
t->majtype = -1;
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
restartrender ();
}
static void
loadit (const gchar * fn)
{
FILE *f;
gchar endbuf[21 * (G_ASCII_DTOSTR_BUF_SIZE + 1)];
gchar *end = endbuf;
gchar line[1024];
gchar fmt_str[16];
gint i;
texture *t;
gint majtype, type;
f = g_fopen (fn, "rt");
if (! f)
{
g_message (_("Could not open '%s' for reading: %s"),
gimp_filename_to_utf8 (fn), g_strerror (errno));
return;
}
if (2 != fscanf (f, "%d %d", &majtype, &type) || majtype < 0 || majtype > 2)
{
g_message (_("File '%s' is not a valid save file."),
gimp_filename_to_utf8 (fn));
fclose (f);
return;
}
rewind (f);
s.com.numtexture = 0;
snprintf (fmt_str, sizeof (fmt_str), "%%d %%d %%%" G_GSIZE_FORMAT "s", sizeof (endbuf) - 1);
while (!feof (f))
{
if (!fgets (line, 1023, f))
break;
i = s.com.numtexture;
t = &s.com.texture[i];
setdefaults (t);
if (sscanf (line, fmt_str, &t->majtype, &t->type, end) != 3)
t->color1.x = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->color1.y = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->color1.z = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->color1.w = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->color2.x = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->color2.y = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->color2.z = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->color2.w = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->oscale = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->turbulence.x = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->amount = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->exp = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->scale.x = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->scale.y = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->scale.z = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->rotate.x = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->rotate.y = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->rotate.z = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->translate.x = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->translate.y = g_ascii_strtod (end, &end);
if (end && errno != ERANGE)
t->translate.z = g_ascii_strtod (end, &end);
s.com.numtexture++;
}
fclose (f);
}
static void
loadpreset_response (GtkWidget *dialog,
gint response_id,
gpointer data)
{
if (response_id == GTK_RESPONSE_OK)
{
GtkTreeModel *model = gtk_tree_view_get_model (texturelist);
gchar *name;
name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
gtk_list_store_clear (GTK_LIST_STORE (model));
loadit (name);
g_free (name);
rebuildlist ();
}
gtk_widget_hide (dialog);
}
static void
saveit (const gchar *fn)
{
gint i;
FILE *f;
gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
f = g_fopen (fn, "wt");
if (!f)
{
g_message (_("Could not open '%s' for writing: %s"),
gimp_filename_to_utf8 (fn), g_strerror (errno));
return;
}
for (i = 0; i < s.com.numtexture; i++)
{
texture *t = &s.com.texture[i];
if (t->majtype < 0)
continue;
fprintf (f, "%d %d", t->majtype, t->type);
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.x));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.y));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.z));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color1.w));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.x));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.y));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.z));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->color2.w));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->oscale));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->turbulence.x));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->amount));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->exp));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->scale.x));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->scale.y));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->scale.z));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->rotate.x));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->rotate.y));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->rotate.z));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->translate.x));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->translate.y));
fprintf (f, " %s", g_ascii_dtostr (buf, sizeof (buf), t->translate.z));
fprintf (f, "\n");
}
fclose (f);
}
static void
savepreset_response (GtkWidget *dialog,
gint response_id,
gpointer data)
{
if (response_id == GTK_RESPONSE_OK)
{
gchar *name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
saveit (name);
g_free (name);
}
gtk_widget_hide (dialog);
}
static void
loadpreset (GtkWidget *widget,
GtkWidget *parent)
{
fileselect (GTK_FILE_CHOOSER_ACTION_OPEN, parent);
}
static void
savepreset (GtkWidget *widget,
GtkWidget *parent)
{
fileselect (GTK_FILE_CHOOSER_ACTION_SAVE, parent);
}
static void
fileselect (GtkFileChooserAction action,
GtkWidget *parent)
{
static GtkWidget *windows[2] = { NULL, NULL };
gchar *titles[] = { N_("Open File"), N_("Save File") };
void *handlers[] = { loadpreset_response, savepreset_response };
if (! windows[action])
{
GtkWidget *dialog = windows[action] =
gtk_file_chooser_dialog_new (gettext (titles[action]),
GTK_WINDOW (parent),
action,
_("_Cancel"), GTK_RESPONSE_CANCEL,
action == GTK_FILE_CHOOSER_ACTION_OPEN ?
_("_Open") : _("_Save"),
GTK_RESPONSE_OK,
NULL);
gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
TRUE);
g_signal_connect (dialog, "destroy",
G_CALLBACK (gtk_widget_destroyed),
&windows[action]);
g_signal_connect (dialog, "response",
G_CALLBACK (handlers[action]),
NULL);
}
gtk_window_present (GTK_WINDOW (windows[action]));
}
static void
initworld (void)
{
gint i;
memset (&world, 0, sizeof (world));
s.com.type = SPHERE;
s.a.x = s.a.y = s.a.z = 0.0;
s.r = 4.0;
/* not: world.obj[0] = s;
* s is a sphere so error C2115: '=' : incompatible types
*/
memcpy (&world.obj[0], &s, sizeof (s));
world.numobj = 1;
world.obj[0].com.numtexture = 0;
world.obj[0].com.numnormal = 0;
for (i = 0; i < s.com.numtexture; i++)
{
common *c = &s.com;
common *d = &world.obj[0].com;
texture *t = &c->texture[i];
if ((t->amount <= 0.0) || (t->majtype < 0))
continue;
if (t->majtype == 0)
{ /* Normal texture */
if (t->type == PHONG)
{
t->phongcolor = t->color1;
t->phongsize = t->oscale / 25.0;
}
d->texture[d->numtexture] = *t;
vmul (&d->texture[d->numtexture].scale,
d->texture[d->numtexture].oscale);
d->numtexture++;
}
else if (t->majtype == 1)
{ /* Bumpmap */
d->normal[d->numnormal] = *t;
vmul (&d->normal[d->numnormal].scale,
d->texture[d->numnormal].oscale);
d->numnormal++;
}
else if (t->majtype == 2)
{ /* Lightsource */
light l;
vcopy (&l.a, &t->translate);
vcopy (&l.color, &t->color1);
vmul (&l.color, t->amount);
world.light[world.numlight] = l;
world.numlight++;
}
}
world.quality = 5;
world.flags |= SMARTAMBIENT;
world.smartambient = 40.0;
}
static gboolean
draw (GtkWidget *widget,
cairo_t *cr)
{
cairo_set_source_surface (cr, buffer, 0.0, 0.0);
cairo_paint (cr);
return TRUE;
}
static void
restartrender (void)
{
if (idle_id)
g_source_remove (idle_id);
idle_id = g_idle_add ((GSourceFunc) render, NULL);
}
static void
selecttexture (GtkWidget *widget,
gpointer data)
{
texture *t;
if (noupdate)
return;
t = currenttexture ();
if (!t)
return;
gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &t->type);
relabel ();
restartrender ();
}
static void
selecttype (GtkWidget *widget,
gpointer data)
{
texture *t;
if (noupdate)
return;
t = currenttexture ();
if (!t)
return;
gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &t->majtype);
relabel ();
restartrender ();
}
static void
getscales (GtkWidget *widget,
gpointer data)
{
gdouble f;
texture *t;
if (noupdate)
return;
t = currenttexture ();
if (!t)
return;
t->amount = gtk_spin_button_get_value (GTK_SPIN_BUTTON (amountscale));
t->exp = gtk_spin_button_get_value (GTK_SPIN_BUTTON (expscale));
f = gtk_spin_button_get_value (GTK_SPIN_BUTTON (turbulencescale));
vset (&t->turbulence, f, f, f);
t->oscale = gtk_spin_button_get_value (GTK_SPIN_BUTTON (scalescale));
t->scale.x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (scalexscale));
t->scale.y = gtk_spin_button_get_value (GTK_SPIN_BUTTON (scaleyscale));
t->scale.z = gtk_spin_button_get_value (GTK_SPIN_BUTTON (scalezscale));
t->rotate.x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (rotxscale));
t->rotate.y = gtk_spin_button_get_value (GTK_SPIN_BUTTON (rotyscale));
t->rotate.z = gtk_spin_button_get_value (GTK_SPIN_BUTTON (rotzscale));
t->translate.x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (posxscale));
t->translate.y = gtk_spin_button_get_value (GTK_SPIN_BUTTON (posyscale));
t->translate.z = gtk_spin_button_get_value (GTK_SPIN_BUTTON (poszscale));
restartrender ();
}
static void
color1_changed (GimpColorButton *button)
{
texture *t = currenttexture ();
if (t)
{
GeglColor *color;
gdouble rgba[4];
color = gimp_color_button_get_color (button);
gegl_color_get_pixel (color, babl_format ("R'G'B'A double"), rgba);
t->color1.x = rgba[0];
t->color1.y = rgba[1];
t->color1.z = rgba[2];
t->color1.w = rgba[3];
restartrender ();
g_object_unref (color);
}
}
static void
color2_changed (GimpColorButton *button)
{
texture *t = currenttexture ();
if (t)
{
GeglColor *color;
gdouble rgba[4];
color = gimp_color_button_get_color (button);
gegl_color_get_pixel (color, babl_format ("R'G'B'A double"), rgba);
t->color2.x = rgba[0];
t->color2.y = rgba[1];
t->color2.z = rgba[2];
t->color2.w = rgba[3];
restartrender ();
g_object_unref (color);
}
}
static void
drawcolor1 (GtkWidget *w)
{
static GtkWidget *lastw = NULL;
GeglColor *color;
gdouble rgba[4];
texture *t = currenttexture ();
if (w)
lastw = w;
else
w = lastw;
if (!w)
return;
if (!t)
return;
color = gegl_color_new (NULL);
rgba[0] = t->color1.x;
rgba[1] = t->color1.y;
rgba[2] = t->color1.z;
rgba[3] = t->color1.w;
gegl_color_set_pixel (color, babl_format ("R'G'B'A double"), rgba);
gimp_color_button_set_color (GIMP_COLOR_BUTTON (w), color);
g_object_unref (color);
}
static void
drawcolor2 (GtkWidget *w)
{
static GtkWidget *lastw = NULL;
GeglColor *color;
gdouble rgba[4];
texture *t = currenttexture ();
if (w)
lastw = w;
else
w = lastw;
if (!w)
return;
if (!t)
return;
color = gegl_color_new (NULL);
rgba[0] = t->color2.x;
rgba[1] = t->color2.y;
rgba[2] = t->color2.z;
rgba[3] = t->color2.w;
gegl_color_set_pixel (color, babl_format ("R'G'B'A double"), rgba);
gimp_color_button_set_color (GIMP_COLOR_BUTTON (w), color);
g_object_unref (color);
}
static gboolean do_run = FALSE;
static void
sphere_response (GtkWidget *widget,
gint response_id,
gpointer data)
{
switch (response_id)
{
case RESPONSE_RESET:
s.com.numtexture = 3;
setdefaults (&s.com.texture[0]);
setdefaults (&s.com.texture[1]);
setdefaults (&s.com.texture[2]);
s.com.texture[1].majtype = 2;
vset (&s.com.texture[1].color1, 1, 1, 1);
vset (&s.com.texture[1].translate, -15, -15, -15);
s.com.texture[2].majtype = 2;
vset (&s.com.texture[2].color1, 0, 0.4, 0.4);
vset (&s.com.texture[2].translate, 15, 15, -15);
gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (texturelist)));
rebuildlist ();
break;
case GTK_RESPONSE_OK:
if (idle_id)
{
g_source_remove (idle_id);
idle_id = 0;
}
do_run = TRUE;
default:
gtk_widget_hide (widget);
gtk_main_quit ();
break;
}
}
static GtkWidget *
makewindow (void)
{
GtkListStore *store;
GtkTreeViewColumn *col;
GtkTreeSelection *selection;
GtkAdjustment *adjustment = NULL;
GtkWidget *window;
GtkWidget *main_hbox;
GtkWidget *main_vbox;
GtkWidget *grid;
GtkWidget *frame;
GtkWidget *scrolled;
GtkWidget *hbox;
GtkWidget *vbox;
GtkWidget *button;
GtkWidget *list;
GeglColor *color = gegl_color_new ("transparent");
window = gimp_dialog_new (_("Sphere Designer"), PLUG_IN_ROLE,
NULL, 0,
gimp_standard_help_func, PLUG_IN_PROC,
_("_Reset"), RESPONSE_RESET,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_OK"), GTK_RESPONSE_OK,
NULL);
gimp_dialog_set_alternative_button_order (GTK_DIALOG (window),
RESPONSE_RESET,
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
gimp_window_set_transient (GTK_WINDOW (window));
g_signal_connect (window, "response",
G_CALLBACK (sphere_response),
NULL);
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))),
main_vbox, TRUE, TRUE, 0);
gtk_widget_show (main_vbox);
main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0);
gtk_widget_show (main_hbox);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
gtk_box_pack_start (GTK_BOX (main_hbox), vbox, FALSE, FALSE, 0);
gtk_widget_show (vbox);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
drawarea = gtk_drawing_area_new ();
gtk_container_add (GTK_CONTAINER (frame), drawarea);
gtk_widget_set_size_request (drawarea, PREVIEWSIZE, PREVIEWSIZE);
gtk_widget_show (drawarea);
g_signal_connect (drawarea, "draw",
G_CALLBACK (draw), NULL);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE);
gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
button = gtk_button_new_with_mnemonic (_("_Open"));
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
g_signal_connect (button, "clicked",
G_CALLBACK (loadpreset),
window);
button = gtk_button_new_with_mnemonic (_("_Save"));
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
g_signal_connect (button, "clicked",
G_CALLBACK (savepreset),
window);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_box_pack_end (GTK_BOX (main_hbox), vbox, TRUE, TRUE, 0);
gtk_widget_show (vbox);
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
GTK_SHADOW_IN);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC);
gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0);
gtk_widget_show (scrolled);
store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
g_object_unref (store);
texturelist = GTK_TREE_VIEW (list);
selection = gtk_tree_view_get_selection (texturelist);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
g_signal_connect (selection, "changed",
G_CALLBACK (selectitem),
NULL);
gtk_widget_set_size_request (list, -1, 150);
gtk_container_add (GTK_CONTAINER (scrolled), list);
gtk_widget_show (list);
col = gtk_tree_view_column_new_with_attributes (_("Layers"),
gtk_cell_renderer_text_new (),
"text", TYPE,
NULL);
gtk_tree_view_append_column (texturelist, col);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
button = gtk_button_new_with_mnemonic (_("_New"));
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
g_signal_connect_swapped (button, "clicked",
G_CALLBACK (addtexture), NULL);
gtk_widget_show (button);
button = gtk_button_new_with_mnemonic (_("D_uplicate"));
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
g_signal_connect_swapped (button, "clicked",
G_CALLBACK (duptexture), NULL);
gtk_widget_show (button);
button = gtk_button_new_with_mnemonic (_("_Delete"));
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
g_signal_connect_swapped (button, "clicked",
G_CALLBACK (deltexture), NULL);
gtk_widget_show (button);
main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, FALSE, FALSE, 0);
gtk_widget_show (main_hbox);
frame = gimp_frame_new (_("Properties"));
gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0);
gtk_widget_show (frame);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (frame), vbox);
gtk_widget_show (vbox);
grid = gtk_grid_new ();
gtk_grid_set_column_spacing (GTK_GRID (grid), 2);
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
gtk_widget_show (grid);
typemenu = gimp_int_combo_box_new (_("Texture"), 0,
_("Bump"), 1,
_("Light"), 2,
NULL);
gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (typemenu), 0,
G_CALLBACK (selecttype),
NULL, NULL);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 0,
_("Type"), 0.0, 0.5,
typemenu, 2);
texturemenu = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL);
{
struct textures_t *t;
for (t = textures; t->s; t++)
gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (texturemenu),
GIMP_INT_STORE_VALUE, t->n,
GIMP_INT_STORE_LABEL, gettext (t->s),
-1);
}
gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (texturemenu), 0,
G_CALLBACK (selecttexture),
NULL, NULL);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
_("Texture"), 0.0, 0.5,
texturemenu, 2);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 2,
_("Colors"), 0.0, 0.5,
hbox, 2);
button = gimp_color_button_new (_("Color Selection Dialog"),
COLORBUTTONWIDTH, COLORBUTTONHEIGHT,
color, GIMP_COLOR_AREA_FLAT);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
drawcolor1 (button);
g_signal_connect (button, "color-changed",
G_CALLBACK (color1_changed),
NULL);
button = gimp_color_button_new (_("Color Selection Dialog"),
COLORBUTTONWIDTH, COLORBUTTONHEIGHT,
color, GIMP_COLOR_AREA_FLAT);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
drawcolor2 (button);
g_signal_connect (button, "color-changed",
G_CALLBACK (color2_changed),
NULL);
adjustment = gtk_adjustment_new (1.0, 0.0, 10.0, 0.1, 1.0, 0.0);
scalescale = gimp_spin_scale_new (adjustment, _("Scale"), 1);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_grid_attach (GTK_GRID (grid), scalescale, 0, 3, 3, 1);
gtk_widget_show (scalescale);
adjustment = gtk_adjustment_new (1.0, 0.0, 10.0, 0.1, 1.0, 0.0);
turbulencescale = gimp_spin_scale_new (adjustment, _("Turbulence"), 1);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_grid_attach (GTK_GRID (grid), turbulencescale, 0, 4, 3, 1);
gtk_widget_show (turbulencescale);
adjustment = gtk_adjustment_new (1.0, 0.0, 1.0, 0.1, 1.0, 0.0);
amountscale = gimp_spin_scale_new (adjustment, _("Amount"), 2);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_grid_attach (GTK_GRID (grid), amountscale, 0, 5, 3, 1);
gtk_widget_show (amountscale);
adjustment = gtk_adjustment_new (1.0, 0.0, 1.0, 0.1, 1.0, 0.0);
expscale = gimp_spin_scale_new (adjustment, _("Exp."), 2);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_grid_attach (GTK_GRID (grid), expscale, 0, 6, 3, 1);
gtk_widget_show (expscale);
frame = gimp_frame_new (_("Transformations"));
gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0);
gtk_widget_show (frame);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_add (GTK_CONTAINER (frame), vbox);
gtk_widget_show (vbox);
adjustment = gtk_adjustment_new (1.0, 0.0, 10.0, 0.1, 1.0, 0.0);
scalexscale = gimp_spin_scale_new (adjustment, _("Scale X"), 2);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), scalexscale, TRUE, TRUE, 0);
gtk_widget_show (scalexscale);
adjustment = gtk_adjustment_new (1.0, 0.0, 10.0, 0.1, 1.0, 0.0);
scaleyscale = gimp_spin_scale_new (adjustment, _("Scale Y"), 2);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), scaleyscale, TRUE, TRUE, 0);
gtk_widget_show (scaleyscale);
adjustment = gtk_adjustment_new (1.0, 0.0, 10.0, 0.1, 1.0, 0.0);
scalezscale = gimp_spin_scale_new (adjustment, _("Scale Z"), 2);
gtk_widget_set_margin_bottom (scalezscale, 6);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), scalezscale, TRUE, TRUE, 0);
gtk_widget_show (scalezscale);
adjustment = gtk_adjustment_new (0.0, 0.0, 360.0, 0.1, 1.0, 0.0);
rotxscale = gimp_spin_scale_new (adjustment, _("Rotate X"), 1);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), rotxscale, TRUE, TRUE, 0);
gtk_widget_show (rotxscale);
adjustment = gtk_adjustment_new (0.0, 0.0, 360.0, 0.1, 1.0, 0.0);
rotyscale = gimp_spin_scale_new (adjustment, _("Rotate Y"), 1);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), rotyscale, TRUE, TRUE, 0);
gtk_widget_show (rotyscale);
adjustment = gtk_adjustment_new (1.0, 0.0, 360.0, 0.1, 1.0, 0.0);
rotzscale = gimp_spin_scale_new (adjustment, _("Rotate Z"), 1);
gtk_widget_set_margin_bottom (rotzscale, 6);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), rotzscale, TRUE, TRUE, 0);
gtk_widget_show (rotzscale);
adjustment = gtk_adjustment_new (0.0, -20.0, 20.0, 0.1, 1.0, 0.0);
posxscale = gimp_spin_scale_new (adjustment, _("Position X"), 1);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), posxscale, TRUE, TRUE, 0);
gtk_widget_show (posxscale);
adjustment = gtk_adjustment_new (0.0, -20.0, 20.0, 0.1, 1.0, 0.0);
posyscale = gimp_spin_scale_new (adjustment, _("Position Y"), 1);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), posyscale, TRUE, TRUE, 0);
gtk_widget_show (posyscale);
adjustment = gtk_adjustment_new (0.0, -20.0, 20.0, 0.1, 1.0, 0.0);
poszscale = gimp_spin_scale_new (adjustment, _("Position Z"), 1);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (getscales),
NULL);
gtk_box_pack_start (GTK_BOX (vbox), poszscale, TRUE, TRUE, 0);
gtk_widget_show (poszscale);
gtk_widget_show (window);
g_object_unref (color);
return window;
}
static guchar
pixelval (gdouble v)
{
v += 0.5;
if (v < 0.0)
return 0;
if (v > 255.0)
return 255;
return v;
}
static gboolean
render (void)
{
GimpVector4 col;
guchar *dest_row;
ray r;
gint x, y, p;
gint tx = PREVIEWSIZE;
gint ty = PREVIEWSIZE;
gint bpp = 4;
idle_id = 0;
initworld ();
r.v1.z = -10.0;
r.v2.z = 0.0;
if (world.obj[0].com.numtexture > 0)
{
cairo_surface_flush (buffer);
for (y = 0; y < ty; y++)
{
dest_row = img + y * img_stride;
for (x = 0; x < tx; x++)
{
gint g, gridsize = 16;
g = ((x / gridsize + y / gridsize) % 2) * 60 + 100;
r.v1.x = r.v2.x = 8.5 * (x / (float) (tx - 1) - 0.5);
r.v1.y = r.v2.y = 8.5 * (y / (float) (ty - 1) - 0.5);
p = x * bpp;
traceray (&r, &col, 10, 1.0);
if (col.w < 0.0)
col.w = 0.0;
else if (col.w > 1.0)
col.w = 1.0;
GIMP_CAIRO_RGB24_SET_PIXEL ((dest_row + p),
pixelval (255 * col.x) * col.w + g * (1.0 - col.w),
pixelval (255 * col.y) * col.w + g * (1.0 - col.w),
pixelval (255 * col.z) * col.w + g * (1.0 - col.w));
}
}
cairo_surface_mark_dirty (buffer);
}
gtk_widget_queue_draw (drawarea);
return FALSE;
}
static void
realrender (GimpDrawable *drawable)
{
GeglBuffer *src_buffer;
GeglBuffer *dest_buffer;
const Babl *format;
gint x, y;
ray r;
GimpVector4 rcol;
gint width, height;
gint x1, y1;
guchar *dest;
gint bpp;
guchar *buffer, *ibuffer;
initworld ();
r.v1.z = -10.0;
r.v2.z = 0.0;
if (! gimp_drawable_mask_intersect (drawable,
&x1, &y1, &width, &height))
return;
src_buffer = gimp_drawable_get_buffer (drawable);
dest_buffer = gimp_drawable_get_shadow_buffer (drawable);
if (gimp_drawable_is_rgb (drawable))
{
if (gimp_drawable_has_alpha (drawable))
format = babl_format ("R'G'B'A u8");
else
format = babl_format ("R'G'B' u8");
}
else
{
if (gimp_drawable_has_alpha (drawable))
format = babl_format ("Y'A u8");
else
format = babl_format ("Y' u8");
}
bpp = babl_format_get_bytes_per_pixel (format);
buffer = g_malloc (width * 4);
ibuffer = g_malloc (width * 4);
gimp_progress_init (_("Rendering sphere"));
for (y = 0; y < height; y++)
{
dest = buffer;
for (x = 0; x < width; x++)
{
r.v1.x = r.v2.x = 8.1 * (x / (float) (width - 1) - 0.5);
r.v1.y = r.v2.y = 8.1 * (y / (float) (height - 1) - 0.5);
traceray (&r, &rcol, 10, 1.0);
dest[0] = pixelval (255 * rcol.x);
dest[1] = pixelval (255 * rcol.y);
dest[2] = pixelval (255 * rcol.z);
dest[3] = pixelval (255 * rcol.w);
dest += 4;
}
gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y1 + y, width, 1), 1.0,
format, ibuffer,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
for (x = 0; x < width; x++)
{
gint k, dx = x * 4, sx = x * bpp;
gfloat a = buffer[dx + 3] / 255.0;
for (k = 0; k < bpp; k++)
{
ibuffer[sx + k] =
buffer[dx + k] * a + ibuffer[sx + k] * (1.0 - a);
}
}
gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (x1, y1 + y, width, 1), 0,
format, ibuffer,
GEGL_AUTO_ROWSTRIDE);
gimp_progress_update ((gdouble) y / (gdouble) height);
}
gimp_progress_update (1.0);
g_free (buffer);
g_free (ibuffer);
g_object_unref (src_buffer);
g_object_unref (dest_buffer);
gimp_drawable_merge_shadow (drawable, TRUE);
gimp_drawable_update (drawable, x1, y1, width, height);
}
static gboolean
sphere_main (GimpDrawable *drawable)
{
gimp_ui_init (PLUG_IN_BINARY);
img_stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, PREVIEWSIZE);
img = g_malloc0 (img_stride * PREVIEWSIZE);
buffer = cairo_image_surface_create_for_data (img, CAIRO_FORMAT_RGB24,
PREVIEWSIZE,
PREVIEWSIZE,
img_stride);
makewindow ();
if (s.com.numtexture == 0)
{
/* Setup and use default list */
sphere_response (NULL, RESPONSE_RESET, NULL);
}
else
{
/* Reuse the list from a previous invocation */
rebuildlist ();
}
gtk_main ();
cairo_surface_destroy (buffer);
g_free (img);
return do_run;
}
static GimpValueArray *
designer_run (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data)
{
GBytes *settings_bytes = NULL;
GimpDrawable *drawable;
gint x, y, w, h;
gegl_init (NULL, NULL);
if (gimp_core_object_array_get_length ((GObject **) drawables) != 1)
{
GError *error = NULL;
g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
_("Procedure '%s' only works with one drawable."),
PLUG_IN_PROC);
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CALLING_ERROR,
error);
}
else
{
drawable = drawables[0];
}
if (! gimp_drawable_mask_intersect (drawable, &x, &y, &w, &h))
{
g_message (_("Region selected for plug-in is empty"));
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_SUCCESS,
NULL);
}
s.com.numtexture = 0;
/* Temporary code replacing legacy gimp_[gs]et_data() using an AUX argument.
* This doesn't actually fix the "Reset to initial values|factory defaults"
* features, but at least makes per-run value storage work.
* TODO: eventually we want proper separate arguments as a complete fix.
*/
g_object_get (config, "settings-data", &settings_bytes, NULL);
if (settings_bytes != NULL && g_bytes_get_size (settings_bytes) == sizeof (sphere))
s = *((sphere *) g_bytes_get_data (settings_bytes, NULL));
g_bytes_unref (settings_bytes);
switch (run_mode)
{
case GIMP_RUN_INTERACTIVE:
if (! sphere_main (drawable))
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CANCEL,
NULL);
break;
case GIMP_RUN_WITH_LAST_VALS:
if (s.com.numtexture == 0)
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_EXECUTION_ERROR,
NULL);
break;
case GIMP_RUN_NONINTERACTIVE:
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_EXECUTION_ERROR,
NULL);
}
settings_bytes = g_bytes_new (&s, sizeof (sphere));
g_object_set (config, "settings-data", settings_bytes, NULL);
g_bytes_unref (settings_bytes);
realrender (drawable);
gimp_displays_flush ();
return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}