From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- target/ofps.c | 10222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 10222 insertions(+) create mode 100644 target/ofps.c (limited to 'target/ofps.c') diff --git a/target/ofps.c b/target/ofps.c new file mode 100644 index 0000000..fdea8cb --- /dev/null +++ b/target/ofps.c @@ -0,0 +1,10222 @@ + +/* + * ArgyllCMS Color Correction System + * + * Optimised Farthest Point Sampling - NN + * + * Author: Graeme W. Gill + * Date: 6/9/2004 + * + * Copyright 2004, 2009 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* Latest version using vertex nets to reduce internal accounting overhead, */ +/* in an attempt to improve performance scaling with larger numbers of points. */ + +/* TTBD: + + This code shouldn't exit on an error - this causes an unnecessary failure + when ofps is used to evaluate the point distribution of other + distribution algorithms. + + There is a bug when the ink limit == dimensions-1 (200% for CMYY), and + the number of bit mask then exceeds > 32. This is not so +/- 0.2% either side + of 200%. + + One way of addressing the performance issues would be to use multiple + threads to call dnsq. A pool could be setup, one for each CPU. + + Some profiles are too rough, and slow/stall vertex placement. + Reducing the cache grid and/or smoothing the rspl values + ay mitigate this to some degree, and make this more robust ?? + + */ + +/* + Description: + + We create a function that estimates the sample positioning error at + any location based on a weighted combination of perceptual and device distance, + and perceptual function curvature. + + We then initially add sampling points at the largest estimated error + verticies of the veronoi natural neighbourhood. + This gives us an optimal distribution measuring in mestimated position + error within a tollerance of 2:1 + + We then iteratively improve the distribution of point nodes by + moving them in the direction of the adjacent vertex with the + largest estimated sampling error, so that the errors are equally + distributed. + + To ensure that there is a good distribution of sampling + points on the edges and faces of the gamut, the initial + points are given a slighte weighting towards these + elements, and then fastened to them. The points on + each lower dimensional element (edge, face) is then + optimized only amongst themselves, while higher + dimension points are aware of the lower dimension + ones. In this way the distribution of points on + lower dimensional surfaces is well spread, while + the higher dimension points take thier positions into account. + */ + +/* + Failings: + + The initial allocation of points to lower dimension surfaces + is a bit haphazard. It would be nice to have some mechanism + to add or subtract points to/from lower dimensional surfaces + if they were over or under sampled compared to everything else. + + While the current algorithm meets many goals, such as minimizing the + maximum estimated error from any point in the space to the nearest + node, and placing nodes on sub dimensional surfaces with distributions + optimal within that sub dimensions, it has one obvious failing, and + that is that it fails to stratify the samples. + + So if a device is dominantly channel indepenedent, it doesn't + take advantage of the number of samples used to fully explore + all possible device channel values. This also applies at + higher dimensions (ie. the CMYK values exploring response + to different K values doesn't spread the CMY values + evenly appart.) + + Stratification seems to be somewhat at odds with the primary goal + of minimizing the maximum estimated error from any point in the + space to the nearest node, but it would be good if there were + some way of setting this balance. + + How could stratification be added to the current approach ? + + In general there are many sub-dimensions views, not all of + which would probably be regarded as important. + + To measure spread, independent voronoi tesselations of + these sub dimensions would be neededi, and they could be + used partly driver optimization (??). + + For 1D device channels this wouldn't be so hard to + add, although it's hard to know how effective it would + be, or whether it would wreck the ND optimization. It + might also be unecessary if per channel calibration + has been applied. + + For CMY this would need a 3D shadow veronoi. + + */ + +#include +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#include "numlib.h" +#include "sort.h" +#include "counters.h" +#include "plot.h" +#include "icc.h" +#include "xicc.h" +#include "xcolorants.h" +#include "targen.h" +#include "rspl.h" +#include "conv.h" +#include "ofps.h" + +//#include + +#undef DEBUG +#undef WARNINGS /* Print warnings within DEBUG */ +#undef STATS /* Show function stats */ + + /* Optimal fully adapted weightings : */ +#define ADAPT_PERCWGHT 0.65 /* Degree of perceptual adaptation */ +#define ADAPT_CURVWGHT 1.0 /* Degree of curvature */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#ifndef STANDALONE_TEST // targen settings + +# define DOOPT /* Do optimization */ +# define INDEP_SURFACE /* Make surface point distribution and optimization independent */ +# undef MAXINDEP_2D /* Limit independent surfaces to 2D */ + /* Seems to be best for ink limited devices to #undef ? */ +# define KEEP_SURFACE /* Keep surface points on the surface during opt. */ +# define INITIAL_SURFACE_PREF 1.50 /* Extra weighting for surface points at start of seeding */ +# define FINAL_SURFACE_PREF 0.80 /* Extra weighting for surface points by end of seeding */ + +# define SURFTOL 0.0001 /* Proportion of average spacing to force to gamut boundary */ +# define RANDOM_PERTERB /* Perpterb initial placement to break up patterns */ + +/* Good mode */ +# define PERTERB_AMOUNT 0.5 /* and to aid surface point distribution with INDEP_SURFACE */ +# define OPT_MAXITS 20 /* Number of optimisation itterations (0 to disable optimisation) */ +# define OPT_TRANS_ITTERS 18 /* Numbers of itterations to transition overshoot and sepw */ +# define OPT_TRANS_POW 1.6 /* Power curve to blend along */ +# define OPT_INITIAL_OVERSHOOT 1.9 /* Optimisation movement initial overshoot */ +# define OPT_FINAL_OVERSHOOT 0.1 /* Optimisation movement final overshoot */ +# define OPT_INITIAL_SEP_WEIGHT 0.7 /* Weight to give separation of nodes during opt */ +# define OPT_FINAL_SEP_WEIGHT 0.3 /* Weight to give separation of nodes during opt */ +# define OPT_STOP_TOL 0.0005 /* Stopping tollerance */ + +/* Fast mode */ +# define PERTERB_AMOUNT_2 0.1 /* and to aid surface point distribution with INDEP_SURFACE */ +# define OPT_MAXITS_2 6 /* Number of optimisation itterations (0 to disable optimisation) */ +# define OPT_TRANS_ITTERS_2 5 /* Numbers of itterations to transition overshoot and sepw */ +# define OPT_TRANS_POW_2 1.7 /* Power curve to blend along */ +# define OPT_INITIAL_OVERSHOOT_2 1.6 /* Optimisation movement initial overshoot */ +# define OPT_FINAL_OVERSHOOT_2 0.05 /* Optimisation movement final overshoot */ +# define OPT_INITIAL_SEP_WEIGHT_2 0.8 /* Weight to give separation of nodes during opt */ +# define OPT_FINAL_SEP_WEIGHT_2 0.3 /* Weight to give separation of nodes during opt */ +# define OPT_STOP_TOL_2 0.001 /* Stopping tollerance */ + +/* Diagnostic settings */ +# undef DUMP_STRUCTURE /* Dump internal node & vertex structure */ +# undef DUMP_PLOT_SEED /* Show on screen plot for each initial seed point */ +# undef DUMP_PLOT /* Show on screen plot after each itteration */ +# define DUMP_VTX 1 /* Display the vertex locations too */ +# define DUMP_PLA 1 /* Display the node planes too */ +# define PERC_PLOT 0 /* Emit perceptive space plots */ +# define DO_WAIT 1 /* Wait for user key after each plot */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#else /* ofps standalone test settings */ + +# define DOOPT /* Do optimization */ +# define INDEP_SURFACE /* Make surface point distribution and optimization independent */ +# define MAXINDEP_2D /* Limit independent surfaces to 2D */ +# define KEEP_SURFACE /* Keep surface points on the surface during opt. */ +# define INITIAL_SURFACE_PREF 1.60 /* Extra weighting for surface points at start of seeding */ +# define FINAL_SURFACE_PREF 0.80 /* Extra weighting for surface points by end of seeding */ + +# define SURFTOL 0.0001 /* Proportion of averag spacing to force to gamut boundary */ +# define RANDOM_PERTERB /* Perpterb initial placement to break up patterns, */ +# define PERTERB_AMOUNT 0.5 + +# define OPT_MAXITS 20 /* Number of optimisation itterations (0 to disable optimisation) */ +# define OPT_TRANS_ITTERS 18 /* Numbers of itterations to transition overshoot and sepw */ +# define OPT_TRANS_POW 2.5 /* Power curve to blend along */ +# define OPT_INITIAL_OVERSHOOT 0.8 /* Optimisation movement initial overshoot */ +# define OPT_FINAL_OVERSHOOT 0.1 /* Optimisation movement final overshoot */ +# define OPT_INITIAL_SEP_WEIGHT 0.9 /* Weight to give separation of nodes during opt */ +# define OPT_FINAL_SEP_WEIGHT 0.3 /* Weight to give separation of nodes during opt */ +# define OPT_STOP_TOL 0.0005 /* Device stopping tollerance */ + +/* Diagnostic settings */ +# undef DUMP_STRUCTURE /* Dump internal node & vertex structure */ +# undef DUMP_PLOT_SEED /* Show on screen plot for each initial seed point */ +# undef DUMP_PLOT_NCOL /* Show on screen plot after adding neighbours, before collecting */ +# define DUMP_PLOT /* Show on screen plot after each itteration */ +# undef DUMP_PLOT_RESEED /* Show on screen plot for each re-seed point */ +# undef DUMP_OPT_PLOT /* Show on screen plot for each optimization pass */ +# undef DUMP_PLOT_BEFORFIXUP /* Show plot after reposition but before fixups */ +# undef DUMP_PLOT_EACHFIXUP /* Show each node fixup */ +# define DUMP_VTX 1 /* Display the vertex locations too */ +# define DUMP_PLA 1 /* Display the node planes too */ +# define PERC_PLOT 0 /* Emit perceptive space plots */ +# define DO_WAIT 1 /* Wait for user key after each plot */ +# undef DUMP_EPERR /* Create .tiff of eperr */ +# undef DUMP_FERR /* 10000 */ /* Create .tiff of function error >= 20 and stop. */ +//# define SA_ADAPT 0.001 /* Standalone test, adaptation level */ + +# define SA_ADAPT -1.0 /* Standalone test, adaptation level (< 0.0, use individual) */ +# define SA_DEVD_MULT 1.0 /* Delta E for each percent of device space distance */ +# define SA_PERC_MULT 0.0 /* Delta E for each delta E of perceptual space distance */ +# define SA_INTERP_MULT 0.0 /* Delta E for each delta E of estimated interpolation error */ + +#endif /* NEVER */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Overall algorithm */ + +#define NINSERTTRIES 100 /* Number of seedin insert tries befor failing with error() */ + +#define NUMTOL 1e-16 /* Numerical tollerance */ +#define FTOL 1e-8 /* dnsqe function tollerance */ +#define FGPMUL 5.0 /* Weighting of gamut plane error into dnsqe function */ +#define COINTOL 1e-8 /* Tollerance for point cooincidence */ +#define ILIMITEPS 1e-6 /* imin, imax and ilimit clip test margine */ + +#define FASTREJMULT1 20.5 /* Fast reject layer distance fudge factor */ +#define FASTREJECTMULT 0.08 /* Fast reject cell skip threshold fudge factor */ + +#define CELLMAXEPERRFF 2.2 /* Cell worst case eperr from center fudge factor */ + +#define TNPAGRID 0.8 /* Target nodes per accelleration & peceptual cache grid cell */ +#define TNPAGRIDMINRES 7 /* Perceptual cache grid min resolution */ +#define TNPAGRIDMAXRES 33 /* Perceptual cache grid max resolution */ +#undef FORCE_INCREMENTAL /* Force incremental update after itteration */ +#undef FORCE_RESEED /* Force reseed after itteration */ +#define MAXTRIES 41 /* Maximum dnsq tries before giving up */ +#define CACHE_PERCEPTUAL /* Cache the perceptual lookup function */ +#define USE_DISJOINT_SETMASKS /* Reduce INDEP_SURFACE setmask size */ + +/* Sanity checks (slow) */ +#undef SANITY_CHECK_SEED /* Sanity check the selection of the bigest eperr seed vertex */ +#undef SANITY_CHECK_HIT /* Sanity check the hit detection */ +#undef SANITY_CHECK_HIT_FATAL /* throw fatal error in sanity check */ +#undef SANITY_CHECK_FIXUP /* Check that fixup was sucessful */ +#undef SANITY_CHECK_FIXUP_FATAL /* throw fatal if it wasn't */ +#undef SANITY_CHECK_CLOSEST /* Check that ofps_findclosest_xx() returns correct result */ +#undef SANITY_CHECK_CLOSEST_FATAL /* throw fatal error on sanity fail */ +#undef SANITY_CHECK_CONSISTENCY /* Check internal consistency at each itteration */ +#undef SANITY_CHECK_CONSISTENCY_FATAL /* Throw a fatal if it wasn't */ + +#undef SANITY_RESEED_AFTER_FIXUPS /* Re-create veronoi from scratch after incremental update. */ +#undef SANITY_CHECK_EXAUSTIVE_SEARCH_FOR_VERTEXES /* Do very, vert slow search for all vertexes */ + +#define ALWAYS +#undef NEVER + +#ifdef STATS +# include "conv.h" /* System dependent convenience functions */ +#endif + +#if defined(DUMP_EPERR) || defined(DUMP_FERR) +#include "tiffio.h" +struct _vopt_cx; +static void dump_dnsqe(ofps *s, char *fname, int *nix, struct _vopt_cx *cx); +#endif + +#if defined(DEBUG) || defined(DUMP_PLOT_SEED) || defined(DUMP_PLOT) +static void dump_image(ofps *s, int pcp, int dwt, int vtx, int dpla, int ferr, int noi); +#endif +#if defined(DEBUG) || defined (SANITY_CHECK_CONSISTENCY) +static void sanity_check(ofps *s, int check_nodelists); +#endif +#if defined(DEBUG) || defined(DUMP_STRUCTURE) +static void dump_node_vtxs(ofps *s, int check_nodelists); +//static void dump_node_vtxs2(ofps *s, char *cc); +#endif + +static void ofps_binit(ofps *s); +static void ofps_stats(ofps *s); +static int ofps_point2cell(ofps *s, double *v, double *p); +static void ofps_gridcoords(ofps *s, int *c, double *v, double *p); +static void ofps_add_nacc(ofps *s, node *n); +static void ofps_rem_nacc(ofps *s, node *n); +static void ofps_add_vacc(ofps *s, vtx *vx); +static void ofps_rem_vacc(ofps *s, vtx *vx); +static void ofps_add_vseed(ofps *s, vtx *vx); +static void ofps_rem_vseed(ofps *s, vtx *vx); +static void ofps_re_create_node_node_vtx_lists(ofps *s); +static void do_batch_update1(ofps *s, int fixup); +static void do_batch_update2(ofps *s, int fixup); + +static node *ofps_findclosest_node(ofps *s, double *ceperr, vtx *vx); +//static vtx *ofps_findclosest_vtx(ofps *s, double *ceperr, node *nn); +static int ofps_findhit_vtxs(ofps *s, node *nn); + +static char *pco(int di, int *co); +static char *ppos(int di, double *p); +static char *pcomb(int di, int *n); +static char *peperr(double eperr); +static char *psm(ofps *s, setmask *sm); + +/* Check the incremental vertexes against the re-seeded vertexes */ +static void save_ivertexes(ofps *s); +static int check_vertexes(ofps *s); + +/* Check that no node is closer to a vertex than its parent */ +static int check_vertex_closest_node(ofps *s); + +/* Do an exaustive check for missing vertexes */ +static void check_for_missing_vertexes(ofps *s); + +/* --------------------------------------------------- */ +/* Setmask manipulation functions */ + +#ifdef USE_DISJOINT_SETMASKS + /* We assume the number of words is <= 1, */ + /* and we can use macros */ + +/* Signal this is a single word mask by using -ve no. of bits */ +#define sm_init(s, nbits) _sm_init(s, -(nbits)) + +#define sm_cp(s, sm_B, sm_A) \ + ((sm_B)->m[0] = (sm_A)->m[0]) + +#define sm_or(s, sm_C, sm_A, sm_B) \ + ((sm_C)->m[0] = (sm_A)->m[0] | (sm_B)->m[0]) + +#define sm_orand(s, sm_D, sm_A, sm_B, sm_C) \ + ((sm_D)->m[0] = (sm_A)->m[0] | ((sm_B)->m[0] & (sm_C)->m[0])) + +#define sm_and(s, sm_C, sm_A, sm_B) \ + ((sm_C)->m[0] = (sm_A)->m[0] & (sm_B)->m[0]) + +#define sm_andnot(s, sm_C, sm_A, sm_B) \ + ((sm_C)->m[0] = (sm_A)->m[0] & (s->lwmask ^ (sm_B)->m[0])) + +#define sm_andand(s, sm_D, sm_A, sm_B, sm_C) \ + ((sm_D)->m[0] = (sm_A)->m[0] & (sm_B)->m[0] & (sm_C)->m[0]) + +#define sm_test(s, sm_A) \ + ((sm_A)->m[0] & s->lwmask) + +#define sm_equal(s, sm_A, sm_B) \ + (((sm_A)->m[0] & s->lwmask) == ((sm_B)->m[0] & s->lwmask)) + +#define sm_andtest(s, sm_A, sm_B) \ + ((sm_A)->m[0] & (sm_B)->m[0]) + +#define sm_andnottest(s, sm_A, sm_B) \ + ((sm_A)->m[0] & (s->lwmask ^ (sm_B)->m[0])) + +#define sm_vtx_vtx(s, v1, v2) \ + ((v1)->vm.m[0] & (v2)->vm.m[0] & s->sc[(v1)->cmask & (v2)->cmask].a_sm.m[0]) + +#define sm_vtx_node(s, vx, nn) \ + ((vx)->vm.m[0] & s->sc[(nn)->pmask].a_sm.m[0] & s->sc[(vx)->cmask & (nn)->pmask].a_sm.m[0]) + +#else + +#define sm_init(s, nbits) _sm_init(s, nbits) +#define sm_cp(s, sm_B, sm_A) _sm_cp(s, sm_B, sm_A) +#define sm_or(s, sm_C, sm_A, sm_B) _sm_or(s, sm_C, sm_A, sm_B) +#define sm_orand(s, sm_D, sm_A, sm_B, sm_C) _sm_orand(s, sm_D, sm_A, sm_B, sm_C) +#define sm_and(s, sm_C, sm_A, sm_B) _sm_and(s, sm_C, sm_A, sm_B) +#define sm_andnot(s, sm_C, sm_A, sm_B) _sm_andnot(s, sm_C, sm_A, sm_B) +#define sm_andand(s, sm_D, sm_A, sm_B, sm_C) _sm_andand(s, sm_D, sm_A, sm_B, sm_C) +#define sm_test(s, sm_A) _sm_test(s, sm_A) +#define sm_equal(s, sm_A, sm_B) _sm_equal(s, sm_A, sm_B) +#define sm_andtest(s, sm_A, sm_B) _sm_andtest(s, sm_A, sm_B) +#define sm_andnottest(s, sm_A, sm_B) _sm_andnottest(s, sm_A, sm_B) +#define sm_vtx_node(s, vx, nn) _sm_vtx_node(s, vx, nn) +#define sm_vtx_vtx(s, v1, v2) _sm_vtx_vtx(s, v1, v2) + +#endif + +/* Compute set mask parameters */ +static void _sm_init(ofps *s, int nbits) { + +//printf("~1 _sm_init with %d bits\n",nbits); + s->bpsmw = sizeof(unsigned int) * 8; + + if (nbits < 0) { /* Macro initialisation */ +#ifdef DEBUG + printf("Disjoint sets being used\n"); +#endif + nbits = -nbits; + if (nbits > s->bpsmw) + error("Attempt to use macro setmasks when nbits %d > a words bits %d",nbits,s->bpsmw); + } + + s->smbits = nbits; + s->nsmw = (s->smbits + s->bpsmw - 1)/s->bpsmw; + s->lwmask = ~0; + s->lwmask >>= s->nsmw * s->bpsmw - s->smbits; /* Number of unused bits */ + if (s->nsmw > MXSMASKW) + error("Not enough words for %d setmask bits, got %d need %d\n",s->smbits,MXSMASKW,s->nsmw); +} + +/* Copy a setmask */ +static void _sm_cp(ofps *s, setmask *sm_B, setmask *sm_A) { + int i; + + for (i = 0; i < s->nsmw; i++) + sm_B->m[i] = sm_A->m[i]; +} + +/* Set the whole mask to zero or one */ +static void sm_set(ofps *s, setmask *sm, int val) { + int i; + unsigned int vv = 0; + + if (val & 1) + vv = ~0; + for (i = 0; i < s->nsmw; i++) + sm->m[i] = vv; + sm->m[i-1] &= s->lwmask; +} + +/* Set the given bit to zero or one */ +static void sm_setbit(ofps *s, setmask *sm, int bit, int val) { + int i; + unsigned int vv = 0; + + if (bit > s->smbits) + error("assert, trying to set bit %d outside setmask size %d",bit,s->smbits); + i = bit / s->bpsmw; + vv = 1 << bit % s->bpsmw; + if (val & 1) + sm->m[i] |= vv; + else + sm->m[i] &= ~vv; +} + +/* C = A | B */ +static void _sm_or(ofps *s, setmask *sm_C, setmask *sm_A, setmask *sm_B) { + int i; + + for (i = 0; i < s->nsmw; i++) + sm_C->m[i] = sm_A->m[i] | sm_B->m[i]; +} + +/* D = A | (B & C) */ +static void _sm_orand(ofps *s, setmask *sm_D, setmask *sm_A, setmask *sm_B, setmask *sm_C) { + int i; + + for (i = 0; i < s->nsmw; i++) + sm_D->m[i] = sm_A->m[i] | (sm_B->m[i] & sm_C->m[i]); +} + +/* C = A & B */ +/* Return zero if result is zero */ +static unsigned int _sm_and(ofps *s, setmask *sm_C, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) + vv |= sm_C->m[i] = sm_A->m[i] & sm_B->m[i]; + return vv; +} + +/* C = A & ~B */ +/* Return zero if result is zero */ +static unsigned int _sm_andnot(ofps *s, setmask *sm_C, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) + vv |= sm_C->m[i] = sm_A->m[i] & ~sm_B->m[i]; + else + vv |= sm_C->m[i] = sm_A->m[i] & (s->lwmask ^ sm_B->m[i]); + } + return vv; +} + +/* D = A & B & C */ +/* Return zero if result is zero */ +static unsigned int _sm_andand(ofps *s, setmask *sm_D, setmask *sm_A, setmask *sm_B, setmask *sm_C) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) + vv |= sm_D->m[i] = sm_A->m[i] & sm_B->m[i] & sm_C->m[i]; + return vv; +} + +/* Return zero if result is zero */ +static unsigned int _sm_test(ofps *s, setmask *sm_A) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) + vv |= sm_A->m[i]; + else + vv |= sm_A->m[i] & s->lwmask; + } + return vv; +} + +/* Return nz if the two are equal */ +static unsigned int _sm_equal(ofps *s, setmask *sm_A, setmask *sm_B) { + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) { + if (sm_A->m[i] != sm_B->m[i]) + return 0; + } else { + if ((sm_A->m[i] & s->lwmask) != (sm_B->m[i] & s->lwmask)) + return 0; + } + } + return 1; +} + +/* A & B and return zero if the result was zero. */ +static unsigned int _sm_andtest(ofps *s, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) + vv |= sm_A->m[i] & sm_B->m[i]; + + return vv; +} + +/* A & ~B and return zero if result is zero */ +static unsigned int _sm_andnottest(ofps *s, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) + vv |= sm_A->m[i] & ~sm_B->m[i]; + else + vv |= sm_A->m[i] & (s->lwmask ^ sm_B->m[i]); + } + return vv; +} + +/* Test if two vertexes interact */ +/* return nz if they do */ +static unsigned int _sm_vtx_vtx(ofps *s, vtx *v1, vtx *v2) { + unsigned int vv = 0; + int i; + +#ifdef USE_DISJOINT_SETMASKS + /* Because the mask bits are re-used across disjoint sets, */ + /* we have to discount any intersection that occurs where */ + /* the two items are disjoint, with the exception of the full-d set. */ + for (i = 0; i < s->nsmw; i++) + vv |= v1->vm.m[i] & v2->vm.m[i] & s->sc[v1->cmask & v2->cmask].a_sm.m[i]; +#else + for (i = 0; i < s->nsmw; i++) + vv |= v1->vm.m[i] & v2->vm.m[i]; +# endif + return vv; +} + +/* Test if a vertex and node interact */ +static unsigned int _sm_vtx_node(ofps *s, vtx *vx, node *nn) { + unsigned int vv = 0; + int i; + + /* Because the mask bits are re-used across disjoint sets, */ + /* we have to discount any intersection that occurs where */ + /* the two items are disjoint, with the exception of the full-d set. */ +#ifdef USE_DISJOINT_SETMASKS + for (i = 0; i < s->nsmw; i++) + vv |= vx->vm.m[i] & s->sc[nn->pmask].a_sm.m[i] & s->sc[vx->cmask & nn->pmask].a_sm.m[i]; +#else + for (i = 0; i < s->nsmw; i++) + vv |= vx->vm.m[i] & s->sc[nn->pmask].a_sm.m[i]; +# endif + return vv; +} + +/* Utility - return a string containing the mask in hex */ +static char *psm(ofps *s, setmask *sm) { + static char buf[5][200]; + static int ix = 0; + int e, f; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + sprintf(bp, "0x"); bp += strlen(bp); + for (f = 0, e = s->nsmw-1; e >= 0; e--) { + if (f || e == 0 || sm->m[e] != 0) { + if (f) { + sprintf(bp, "%08x", sm->m[e]); bp += strlen(bp); + } else { + sprintf(bp, "%x", sm->m[e]); bp += strlen(bp); + f = 1; + } + } + } + return buf[ix]; +} + +/* --------------------------------------------------- */ +/* Swap the location of a node in s->n[]. This is assumed to */ +/* be done _before_ a node is added to the veroni */ +static void swap_nodes(ofps *s, int i, int j) { + node *n; + int xx; + + n = s->n[i]; + s->n[i] = s->n[j]; + s->n[j] = n; + + /* fix index number */ + xx = s->n[i]->ix; + s->n[i]->ix = s->n[j]->ix; + s->n[j]->ix = xx; + + xx = s->n[i]->ixm; + s->n[i]->ixm = s->n[j]->ixm; + s->n[j]->ixm = xx; +} + +/* Shuffle all the nodes in the list along to put */ +/* the given node at the start. */ +static void move_node_to_front(ofps *s, int i) { + node *n; + int j; + + n = s->n[i]; + + for (j = 1; j <= i; j++) + s->n[j] = s->n[j-1]; + + s->n[0] = n; + + /* Fix ->ix and ixm */ + for (j = 0; j <= i; j++) { + int bitp; + s->n[j]->ix = i; + + bitp = 31 & (j + (j >> 4) + (j >> 8) + (j >> 12)); + s->n[j]->ixm = (1 << bitp); + } +} + +/* Randomly shuffle all the nodes */ +static void shuffle_node_order(ofps *s) { + int i; + + for (i = 0; i < s->tinp; i++) { + swap_nodes(s, i, i_rand(0, s->tinp-1)); + } +} + +/* Reverse the nodes order */ +static void reverse_node_order(ofps *s) { + int i, j; + + for (i = 0, j = s->tinp-1; i < j; i++, j--) { + swap_nodes(s, i, j); + } +} + +/* --------------------------------------------------- */ +/* Default convert the nodes device coordinates into approximate perceptual coordinates */ +/* (usually overriden by caller supplied function) */ +static void +default_ofps_to_percept(void *od, double *p, double *d) { + ofps *s = (ofps *)od; + int e; + + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < s->di; e++) { + double tt = d[e]; + p[e] = tt * 100.0; + } +} + +/* Cached perceptual lookup */ +static void +ofps_cache_percept(void *od, double *p, double *d) { + int e; + co tp; + rspl *pc = (rspl *)od; + + for (e = 0; e < pc->di; e++) + tp.p[e] = d[e]; + pc->interp(pc, &tp); + for (e = 0; e < pc->fdi; e++) + p[e] = tp.v[e]; +} + +/* Return the distance of the device value from the device gamut */ +/* This will be -ve if the point is outside */ +/* If bvp is non-null, the index of the closest dim times 2 */ +/* will be returned for the 0.0 boundary, dim * 2 + 1 for the 1.0 */ +/* boundary, and di * 2 for the ink limit boundary. */ +static double +ofps_in_dev_gamut(ofps *s, double *d, int *bvp) { + int e, di = s->di; + double tt; + double dd = 100.0; /* Worst distance outside */ + double ss = 0.0; /* Sum of values */ + int bv = di; + for (e = 0; e < di; e++) { + tt = d[e] - s->imin[e]; + if (tt < dd) { + dd = tt; + bv = e * 2; + } + tt = s->imax[e] - d[e]; + if (tt < dd) { + dd = tt; + bv = e * 2 + 1; + } + ss += d[e]; /* Track sum */ + } + ss = (s->ilimit - ss)/di; /* Axis aligned distance to ink limit */ + tt = sqrt((double)di) * ss; /* Diagonal distance to ink limit */ + if (tt < dd) { + dd = tt; + bv = di * 2; + } + if (bvp != NULL) + *bvp = bv; + return dd; +} + +#ifdef NEVER /* Allow performance trace on ofps_clip_point usage */ +static int ofps_clip_point(ofps *s, double *cd, double *d); + +static int ofps_clip_point1(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point2(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point3(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point4(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point5(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point6(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point7(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point8(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point9(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point10(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } + +#else /* Production code */ +#define ofps_clip_point1 ofps_clip_point +#define ofps_clip_point2 ofps_clip_point +#define ofps_clip_point3 ofps_clip_point +#define ofps_clip_point4 ofps_clip_point +#define ofps_clip_point5 ofps_clip_point +#define ofps_clip_point6 ofps_clip_point +#define ofps_clip_point7 ofps_clip_point +#define ofps_clip_point8 ofps_clip_point +#define ofps_clip_point9 ofps_clip_point +#define ofps_clip_point10 ofps_clip_point +#endif + +/* Given the new intended device coordinates, */ +/* clip the new position to the device gamut edge */ +/* return non-zero if the point was clipped */ +static int +ofps_clip_point(ofps *s, double *cd, double *d) { + int di = s->di; + double ss = 0.0; + int rv = 0; + +#define STEP(IX) \ + cd[IX] = d[IX]; \ + if (cd[IX] < s->imin[IX]) { \ + cd[IX] = s->imin[IX]; \ + if (cd[IX] < (s->imin[IX] - ILIMITEPS)) \ + rv |= 1; \ + } else if (cd[IX] > s->imax[IX]) { \ + cd[IX] = s->imax[IX]; \ + if (cd[IX] > (s->imax[IX] + ILIMITEPS)) \ + rv |= 1; \ + } \ + ss += cd[IX]; + + switch (di) { + case 4: + STEP(3) + case 3: + STEP(2) + case 2: + STEP(1) + case 1: + STEP(0) + } +#undef STEP + if (ss > s->ilimit) { + if (ss > (s->ilimit + ILIMITEPS)) + rv |= 1; + ss = (ss - s->ilimit)/s->di; + switch (di) { + case 4: + cd[3] -= ss; + case 3: + cd[2] -= ss; + case 2: + cd[1] -= ss; + case 1: + cd[0] -= ss; + } + } + return rv; +} + +/* Given the new intended device coordinates, */ +/* return non-zero if the point would be clipped. */ +static int +ofps_would_clip_point(ofps *s, double *d) { + int e; + double ss; + for (ss = 0.0, e = 0; e < s->di; e++) { + if (d[e] < (s->imin[e] - ILIMITEPS)) + return 1; + else if (d[e] > (s->imax[e] + ILIMITEPS)) + return 1; + ss += d[e]; + } + if (ss > (s->ilimit + ILIMITEPS)) + return 1; + return 0; +} + +/* Return a out of gamut value. */ +/* 0.0 is returned if the posistion is in gamut */ +static double ofps_oog(ofps *s, double *p) { + int e, di = s->di; + double ss, oog = 0.0; + + for (ss = 0.0, e = 0; e < di; e++) { + if (p[e] < (s->imin[e])) { + double tt = s->imin[e] - p[e]; + if (tt > oog) oog = tt; + } else if (p[e] > (s->imax[e])) { + double tt = p[e] - s->imax[e]; + if (tt > oog) oog = tt; + } + ss += p[e]; + } + if (ss > s->ilimit) { + double tt; + ss = (ss - s->ilimit)/di; /* Axis aligned distance to ink limit */ + tt = sqrt((double)di) * ss; /* Diagonal distance to ink limit */ + if (tt > oog) + oog = tt; + } + return oog; +} + +/* Unbounded perceptual lookup. */ +/* return nz if it was actually clipped and extended */ +static int ofps_cc_percept(ofps *s, double *v, double *p) { + co cp; + int clip; + + clip = ofps_clip_point(s, cp.p, p); + + if (s->pcache) { /* In line this for speed */ + int e, di = s->di; + + s->pcache->interp(s->pcache, &cp); + for (e = 0; e < di; e++) + v[e] = cp.v[e]; + + } else { + s->percept(s->od, v, cp.p); + } + + /* Extend perceptual value using matrix model */ + if (clip) { + int e, di = s->di; + double mcv[MXPD], zv[MXPD]; + +#ifdef DEBUG + if (s->pmod_init == 0) + error("ofps_cc_percept() called before pmod has been inited"); +#endif + /* Lookup matrix mode of perceptual at clipped device */ + icxCubeInterp(s->pmod, di, di, mcv, cp.p); + + /* Compute a correction factor to add to the matrix model to */ + /* give the actual perceptual value at the clipped location */ + for (e = 0; e < di; e++) + zv[e] = v[e] - mcv[e]; + + /* Compute the unclipped matrix model perceptual value */ + icxCubeInterp(s->pmod, di, di, v, p); + + /* Add the correction value to it */ + for (e = 0; e < di; e++) + v[e] += zv[e]; + } + return clip; +} + +/* --------------------------------------------------- */ +/* Vertex alloc/free support */ + +/* Check if a vertex is in the cache index, */ +/* and return it if it is. Return NULL otherwise */ +static vtx *vtx_cache_get(ofps *s, int *nix) { + int e, di = s->di; + unsigned int hash; + vtx *vx; + + hash = (unsigned int)nix[MXPD+1]; /* We assume it was put there by sort */ + + for (vx = s->vch[hash]; vx != NULL; vx = vx->chn) { + for (e = 0; e <= di; e++) { /* See if it is a match */ + if (nix[e] != vx->nix[e]) + break; + } + if (e > di) { /* It is */ + return vx; + } + } + return vx; +} + +/* Add a vertex to the cache index */ +static void vtx_cache_add(ofps *s, vtx *vv) { + int e, di = s->di; + unsigned int hash; + + hash = (unsigned int)vv->nix[MXPD+1]; + + /* Add it to the list */ + vv->chn = s->vch[hash]; + if (s->vch[hash] != NULL) + s->vch[hash]->pchn = &vv->chn; + s->vch[hash] = vv; + vv->pchn = &s->vch[hash]; +} + +/* Remove a vertex from the cache index */ +static void vtx_cache_rem(ofps *s, vtx *vv) { + int e, di = s->di; + unsigned int hash; + vtx *vx; + + hash = (unsigned int)vv->nix[MXPD+1]; + + for (vx = s->vch[hash]; vx != NULL; vx = vx->chn) { + if (vx == vv) { + if (vx->pchn != NULL) { + *vx->pchn = vx->chn; + if (vx->chn != NULL) + vx->chn->pchn = vx->pchn; + } + return; + } + } + /* Hmm. not in cache */ +} + +/* Each vtx returned gets a unique serial number */ +static vtx *new_vtx(ofps *s) { + vtx *vv; + + if (s->fvtx != NULL) { /* re-use one we've got */ + vv = s->fvtx; + s->fvtx = vv->link; + memset((void *)vv, 0, sizeof(vtx)); + + } else { + if ((vv = (vtx *)calloc(sizeof(vtx), 1)) == NULL) + error("ofps: malloc failed on new vertex"); + } + + /* Link vertex to currently used list */ + vv->link = s->uvtx; + if (s->uvtx != NULL) + s->uvtx->plp = &vv->link; + s->uvtx = vv; + vv->plp = &s->uvtx; + vv->no = s->nxvno++; + s->nv++; + + s->nvtxcreated++; + + vv->fuptol = NUMTOL; + + return vv; +} + +/* Remove a vertx from the used list, and put it on the hidden list. */ +/* (Used for making inside and outside vertexes unavailabe) */ +static void remu_vtx(ofps *s, vtx *v) { +//printf("~1 remu_vtx called on no %d\n",v->no); + + /* Remove it from the used list */ + if (v->plp != NULL) { /* If is on used list, remove it */ + *v->plp = v->link; + if (v->link != NULL) + v->link->plp = v->plp; + } + v->plp = NULL; + v->link = NULL; + + /* Add it to the hidden list */ + v->link = s->hvtx; + if (s->hvtx != NULL) + s->hvtx->plp = &v->link; + s->hvtx = v; + v->plp = &s->hvtx; + + s->nv--; /* Don't count hidden verts */ +} + +/* Remove a vertex from the cache and spatial accelleration grid, */ +/* and and the used list. */ +static void del_vtx1(ofps *s, vtx *vx) { + node *nn, *nnn; + + if (vx->plp != NULL) { /* If is on used list, remove it */ + *vx->plp = vx->link; + if (vx->link != NULL) + vx->link->plp = vx->plp; + s->nv--; + } + + if (vx->pfchl != NULL) { /* If is on fixup check list, remove it */ + *vx->pfchl = vx->fchl; + if (vx->fchl != NULL) + vx->fchl->pfchl = vx->pfchl; + } + vx->pfchl = NULL; + vx->fchl = NULL; + + if (vx->ofake == 0) { + + /* Remove it from cache */ + vtx_cache_rem(s, vx); + + /* Remove it from spatial accelleration grid */ + ofps_rem_vacc(s, vx); + + /* Remove it from seeding group */ + ofps_rem_vseed(s, vx); + } + + /* Remove vertex from the s->svtxs[] list */ + /* so that it doesn't get used in fixups. */ + if (vx->psvtxs != NULL) { + *vx->psvtxs = NULL; + vx->psvtxs = NULL; + } +} + +/* Delete a vertex by removing it from the cache and spatial accelleration grid, */ +/* and then moving it to the free list */ +/* (It's assumed that it's been removed from all other */ +/* structures by the caller) */ +static void del_vtx(ofps *s, vtx *vx) { + +//printf("~1 del_vtx called on no %d\n",vx->no); + + /* Remove it from various lists */ + del_vtx1(s, vx); + + /* Free vertex net neighbours list */ + if (vx->nv != NULL) { +/* !!!! if we need this, we're referencing deleted vertexes !!!! */ +/* vx->nnv = vx->_nnv = 0; */ + free(vx->nv); +/* vx->nv = NULL; */ + } + + /* Add to free list */ + vx->link = s->fvtx; + vx->plp = NULL; + s->fvtx = vx; + + s->nvtxdeleted++; +} + +/* Add a vertex to a vertex's net */ +static void vtx_add_vertex(ofps *s, vtx *vv, vtx *vx) { + int i; + +//printf("~1 Adding vertex no %d comb %s vm %s to vtx %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),vv->no); +//printf("~1 Adding vertex no %d to vtx no %d\n",vx->no,vv->no); +//printf("~1 Before add, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); + if (vv->_nnv == 0) { + vv->_nnv = 4; /* Initial allocation */ + if ((vv->nv = (vtx **)malloc(sizeof(vtx *) * vv->_nnv)) == NULL) + error("ofps: malloc failed on node vertex pointers"); + } else if (vv->nnv >= vv->_nnv) { + vv->_nnv *= 2; /* Double allocation */ + if ((vv->nv = (vtx **)realloc(vv->nv, sizeof(vtx *) * vv->_nnv)) == NULL) + error("ofps: realloc failed on node vertex pointers"); + } +#ifdef DEBUG +{ + int i; + + /* Check that we're not adding ourself */ + if (vx == vv) { + printf("Adding vtx no %d comb %s to itself!\n",vx->no,pcomb(s->di,vx->nix)); fflush(stdout); + error("Adding vtx no %d comb %s to itself!\n",vx->no,pcomb(s->di,vx->nix)); + } + + /* Check that the vertex is not already here */ + for (i = 0; i < vv->nnv; i++) { + if (vx == vv->nv[i]) { + printf("Adding vtx no %d comb %s to vtx %d when already there!\n",vx->no,pcomb(s->di,vx->nix),vv->no); fflush(stdout); + fprintf(stderr,"Adding vtx no %d comb %s to vtx %d when already there!\n",vx->no,pcomb(s->di,vx->nix),vv->no); fflush(stdout); +//*((char *)0) = 55; + return; + } + } +} +#endif /* DEBUG */ + + vv->nv[vv->nnv++] = vx; + +//printf("~1 After add, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); +} + +/* Delete a vertex to a vertex's net */ +static void vtx_rem_vertex(ofps *s, vtx *vv, vtx *vx) { + int i, j; + +//printf("~1 Removing vertex no %d comb %s vm %s from vtx %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),vv->no); +//printf("~1 Before delete, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); + + for (i = j = 0; i < vv->nnv; i++) { + if (vv->nv[i] != vx) { + vv->nv[j] = vv->nv[i]; + j++; + } + } + vv->nnv = j; +//printf("~1 After delete, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); +} + +/* Given two vertexes, check if they are neighbours, and if they are, */ +/* add them to each others neighbourhood. */ +/* If fixup is set, first check that they arn't already neighbours */ +/* Return nz if ther were added to each other */ +static int vtx_cnd_biadd_vtx(ofps *s, vtx *vx1, vtx *vx2, int fixup) { + int f, ff, e, di = s->di; + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + + if (vx1 == vx2) + return 0; + +#ifdef NEVER /* vertex net needs all neighbours ? */ +#ifdef INDEP_SURFACE + /* Can only have a net between them if they are visible to each other */ + if (sm_vtx_vtx(s, vx1, vx2) == 0) + return 0; +#endif +#endif + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = vx1->nix[MXPD+2]; /* nixm */ + bb = vx2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) { + return 0; /* It's certainly not */ + } + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (vx1->nix[e] == vx2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (vx1->nix[e] > vx2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) { + return 0; /* No match */ + } + + if (nnm == 0) { + fflush(stdout); + error("ofps: two vertexes have the same nodes !\n" + "no %d at %s nix %s\nno %d at %s nix %s", + vx1->no,ppos(di,vx1->p),pcomb(di,vx1->nix), + vx2->no,ppos(di,vx2->p),pcomb(di,vx2->nix)); + } + + /* If fixup or not INDEP_SURFACE, check that the vertex */ + /* is not already here */ +#ifndef INDEP_SURFACE + if (fixup) +#endif + { + int i; + vtx *va = vx1, *vb = vx2; + + if (vx1->nnv > vx2->nnv) { /* Search the shortest list */ + va = vx2; + vb = vx1; + } + for (i = 0; i < va->nnv; i++) { + if (vb == va->nv[i]) { + return 0; + } + } + } + +//printf("~1 Adding net between vtx no %d and no %d\n",vx1->no,vx2->no); + /* vx2 is a neighbour, so add it to the vtx net */ + vtx_add_vertex(s, vx1, vx2); + + /* The reverse must apply too */ + vtx_add_vertex(s, vx2, vx1); + + return 1; +} + +#ifdef NEVER /* Not used */ + +/* Clear any veroinoi content of a vertex, but not the vertex itself. */ +static void vtx_clear(ofps *s, vtx *v) { + + /* Clear the list of vertex net neighbours */ + v->nnv = 0; +} + +/* Free any allocated content of a vertex, but not the vertex itself. */ +static void vtx_free(ofps *s, vtx *v) { + +//printf("~1 freeing node ix %d and all contents\n",v->ix); + + /* Free up list of Vertex net neighbours */ + if (v->nv != NULL) { + v->nnv = v->_nnv = 0; + free(v->nv); + v->nv = NULL; + } +} +#endif /* NEVER */ + +/* vertex binary tree support */ + +static int vtx_aat_cmp_eperr(const void *p1, const void *p2) { + return ((vtx *)p1)->eperr == ((vtx *)p2)->eperr ? 0 : + (((vtx *)p1)->eperr < ((vtx *)p2)->eperr ? -1 : 1); +} + +static int vtx_aat_cmp_eserr(const void *p1, const void *p2) { + return ((vtx *)p1)->eserr == ((vtx *)p2)->eserr ? 0 : + (((vtx *)p1)->eserr < ((vtx *)p2)->eserr ? -1 : 1); +} + + +/* --------------------------------------------------- */ +/* Midpoint alloc/free support */ + +/* Each mid returned gets a unique serial number */ +/* and a refc or 0 */ +static mid *new_mid(ofps *s) { + mid *p; + + if (s->fmid != NULL) { /* re-use one we've got */ + p = s->fmid; + s->fmid = p->link; + memset((void *)p, 0, sizeof(mid)); + + } else { + if ((p = (mid *)calloc(sizeof(mid), 1)) == NULL) + error("ofps: malloc failed on new midpoint"); + } + + /* Link midpoint to currently used list */ + p->link = s->umid; + if (s->umid != NULL) + s->umid->plp = &p->link; + s->umid = p; + p->plp = &s->umid; + p->no = s->nxmno++; + + return p; +} + +/* Decrement reference count, and midpoint to the free list */ +static void del_mid(ofps *s, mid *p) { +//printf("~1 del_mid called on no %d, refc = %d\n",p->no,p->refc); + if (--p->refc <= 0) { + + if (p->plp != NULL) { /* If is on used list, remove it */ + *p->plp = p->link; + if (p->link != NULL) + p->link->plp = p->plp; + } + + p->link = s->fmid; /* Add to free list */ + p->plp = NULL; + s->fmid = p; + p->refc = 0; + } +} + +/* --------------------------------------------------- */ +/* Node basic support functions */ + +/* Clear any veroinoi content of a node, but not the node itself. */ +static void node_clear(ofps *s, node *p) { + + /* Clear the list of Voronoi verticies */ + p->nvv = 0; + + /* Clear any midpoints and nodes */ + while (p->nvn > 0) { + if (p->mm[--p->nvn] != NULL) + del_mid(s, p->mm[p->nvn]); + } +} + +/* Free any allocated content of a node, but not the node itself. */ +static void node_free(ofps *s, node *p) { + +//printf("~1 freeing node ix %d and all contents\n",p->ix); + + /* Free up list of Voronoi verticies */ + if (p->vv != NULL) { + free(p->vv); + p->vv = NULL; + p->nvv = p->_nvv = 0; + } + + /* Free up list of voronoi node indexes */ + if (p->vn != NULL) { + while (p->nvn > 0) { + if (p->mm[--p->nvn] != NULL) + del_mid(s, p->mm[p->nvn]); + } + p->nvn = p->_nvn = 0; + free(p->vn); + free(p->mm); + p->vn = NULL; + p->mm = NULL; + } + + p->nsp = 0; /* No list of surface planes */ +} + +/* Add a vertex to the node vertex list */ +static void node_add_vertex(ofps *s, node *pp, vtx *vx) { + +//printf("~1 Adding vertex no %d comb %s vm %s to node %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),pp->ix); +#ifdef DEBUG + if (vx->del) + warning("!!!!! adding vertex no %d with delete flag set!!!",vx->no); +#endif + + if (pp->_nvv == 0) { + pp->_nvv = 4; /* Initial allocation */ + if ((pp->vv = (vtx **)malloc(sizeof(vtx *) * pp->_nvv)) == NULL) + error("ofps: malloc failed on node vertex pointers"); + } else if (pp->nvv >= pp->_nvv) { + pp->_nvv *= 2; /* Double allocation */ + if ((pp->vv = (vtx **)realloc(pp->vv, sizeof(vtx *) * pp->_nvv)) == NULL) + error("ofps: realloc failed on node vertex pointers"); + } + +#ifdef DEBUG +{ + int i; + + /* Check that the vertex is not already here */ + for (i = 0; i < pp->nvv; i++) { + if (vx == pp->vv[i]) { + printf("Adding vtx no %d comb %s when already there!\n",vx->no,pcomb(s->di,vx->nix)); fflush(stdout); + error("Adding vtx no %d comb %s when already there!",vx->no,pcomb(s->di,vx->nix)); fflush(stdout); + } + } +} +#endif /* DEBUG */ + + pp->vv[pp->nvv++] = vx; + +#ifdef NEVER +#ifdef DEBUG + { + int e, di = s->di; + printf("~1 +++ Node ix %d add vtx no %d pos %s err %f @ %d:",pp->ix,vx->no,ppos(di,vx->p),vx->eperr,pp->nvv); + for (e = 0; e < pp->nvv; e++) + printf("%d ",pp->vv[e]->no); + printf("\n"); + } +#endif +#endif +} + +/* Remove a vertex from the node vertex list */ +static void node_rem_vertex(ofps *s, node *pp, vtx *vx) { + int i, j; + +//printf("~1 Removing vertex no %d comb %s vm %s from node ix %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),pp->ix); +//printf("~1 Before delete, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); + + for (i = j = 0; i < pp->nvv; i++) { + if (pp->vv[i] != vx) { + pp->vv[j] = pp->vv[i]; + j++; + } + } + pp->nvv = j; +} + +/* Add a node index to the node */ +static void node_add_nix(ofps *s, node *pp, int ix) { + + if (pp->_nvn == 0) { + pp->_nvn = 4; /* Initial allocation */ + if ((pp->vn = (int *)malloc(sizeof(int) * pp->_nvn)) == NULL) + error("ofps: malloc failed on node index list"); + if ((pp->mm = (mid **)malloc(sizeof(mid *) * pp->_nvn)) == NULL) + error("ofps: malloc failed on midpoint pointer list"); + } else if (pp->nvn >= pp->_nvn) { + pp->_nvn *= 2; /* Double allocation */ + if ((pp->vn = (int *)realloc(pp->vn, sizeof(int) * pp->_nvn)) == NULL) + error("ofps: realloc failed on node index list"); + if ((pp->mm = (mid **)realloc(pp->mm, sizeof(mid *) * pp->_nvn)) == NULL) + error("ofps: realloc failed on midpoint pointer list"); + } + pp->vn[pp->nvn] = ix; + pp->mm[pp->nvn++] = NULL; + +#ifdef NEVER +#ifdef DEBUG + { + int e, di = s->di; + printf("~1 +++ Node ix %d add node ix %d at %s @ %d: ",pp->ix,ix,ppos(di,pp->p),pp->nvn); + for (e = 0; e < pp->nvn; e++) + printf("%d ",pp->vn[e]); + printf("\n"); + } +#endif +#endif +} + +/* Recompute a nodes neighborhood nodes. */ +/* (Invalidates and deletes any midpoints) */ +static void node_recomp_nvn( + ofps *s, + node *pp +) { + int e, di = s->di; + int i, j, k; + +#ifdef DEBUG + printf("node_recomp_nvn for node ix %d\n",pp->ix); +#endif + /* Clear any midpoints and nodes */ + while (pp->nvn > 0) { + if (pp->mm[--pp->nvn] != NULL) + del_mid(s, pp->mm[pp->nvn]); + } + s->nvnflag++; /* Make sure each node is only added once */ + pp->nvnflag = s->nvnflag; /* Don't put self in list */ + + for (i = 0; i < pp->nvv; i++) { /* For each vertex */ + double rads; + vtx *vv = pp->vv[i]; + + for (j = 0; j <= di; j++) { /* For each node in vertex */ + int ix = vv->nix[j]; + node *ap = s->n[ix]; + + if (ap->nvnflag == s->nvnflag) + continue; /* Already done that node */ + + node_add_nix(s, pp, ix); + ap->nvnflag = s->nvnflag; /* Don't worry about it again */ + } + } +} + +/* Sort a vertex node index array of di+1 nodes, */ +/* and add a hash at MXDP+1, and nixm at MXDP+2 */ +/* This is to speed up searching for a match */ +/* Sort largest to smallest (so fake gamut nodes are last) */ +static void sort_nix(ofps *s, int *nix) { + int i, j, t; + int di = s->di; /* There are di+1 nodes */ + unsigned int hash = 0; + int nixm = 0; + + /* Do a really simple exchange sort */ + for (i = 0; i < di; i++) { + for (j = i+1; j <= di; j++) { + if (nix[i] < nix[j]) { + t = nix[j]; + nix[j] = nix[i]; + nix[i] = t; + } + } + } + + /* And then compute the hash and nixm */ + for (i = 0; i <= di; i++) { + int bitp, ix = nix[i]; + hash = hash * 17 + nix[i]; + bitp = 31 & (ix + (ix >> 4) + (ix >> 8) + (ix >> 12)); + nixm |= (1 << bitp); + } + hash %= VTXCHSIZE; + + nix[MXPD+1] = (int)hash; + nix[MXPD+2] = nixm; +} + +/* Check if the given locate is on the gamut boundary surface, */ +/* and return the corresponding plane mask */ +static unsigned int check_pos_gsurf(ofps *s, double *p) { + int i, e, di = s->di; + unsigned int pmask = 0; + + /* For all the gamut boundary planes */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * p[e]; + /* See if this location close to, or outside boundary plane */ + if (v > -s->surftol) + pmask |= (1 << i); + } +#ifdef MAXINDEP_2D + if (s->sc[pmask].valid == 0) + pmask = 0; +#endif + return pmask; +} + +/* Check if the given node is on the gamut boundary surface, */ +/* and record the number of surfaces it is on. */ +/* Return nz if the node is on one or more gamut boundaries. */ +/* Set the state of the node clip flag too. */ +static int det_node_gsurf(ofps *s, node *n, double *p) { + int i, e, di = s->di; + double ss; + + n->nsp = 0; + n->pmask = 0; + + /* For all the gamut boundary planes */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * p[e]; + + /* See if this location close to, or outside boundary plane */ + if (v > -s->surftol) { + /* Add pointer to plane it falls on */ + n->sp[n->nsp++] = vp; + n->pmask |= (1 << i); + if (n->nsp > MXPD+1) + error("Assert in ofps det_node_gsurf : nsp %d > MXPD +1 %d",n->nsp, MXPD+1); + } + } + +#ifdef MAXINDEP_2D + if (s->sc[n->pmask].valid == 0) { + n->pmask = 0; + n->nsp = 0; + } +#endif +//printf("~1 node pmask = 0x%x\n",n->pmask); + + return (n->nsp > 0); +} + +/* Compute a cmask from an nix */ +static unsigned int comp_cmask(ofps *s, int *nix) { + unsigned int smask = 0, cmask = ~0; + int i, e, di = s->di; + + /* The composition mask indicates all the common surface planes */ + /* that a vertexes parent nodes lie on. Given this, one expects */ + /* the resulting location to be the same of within this. */ + for (e = 0; e <= di; e++) { + int ix = nix[e]; + if (ix < 0 && ix >= -s->nbp) { /* If fake surface node */ + smask |= 1 << -ix-1; + } else if (ix >= 0) { /* If real node */ + cmask &= s->n[ix]->pmask; + } + } + if (smask != 0) + cmask &= smask; + +#ifdef MAXINDEP_2D + if (s->sc[cmask].valid == 0) + cmask = 0; +#endif + + return cmask; +} + +/* Check if the given vertex is on the gamut boundary surface, */ +/* and record the number of surfaces it is on. */ +/* Also compute its cmask based on its parent nodes. */ +/* Set the state of the clip flag too. */ +static void det_vtx_gsurf(ofps *s, vtx *vx) { + int i, e, di = s->di; + unsigned int smask = 0; + + vx->nsp = 0; + + /* The composition mask indicates all the common surface planes */ + /* that a vertexes parent nodes lie on. Given this, one expects */ + /* the resulting location to be the same of within this. */ + vx->cmask = ~0; + for (e = 0; e <= di; e++) { + int ix = vx->nix[e]; + if (ix < 0 && ix >= -s->nbp) { /* If fake surface node */ + smask |= 1 << -ix-1; + } else if (ix >= 0) { + vx->cmask &= s->n[ix]->pmask; + } + } + if (smask != 0) + vx->cmask &= smask; + +#ifdef MAXINDEP_2D + if (s->sc[vx->cmask].valid == 0) + vx->cmask = 0; +#endif + + /* For all the gamut boundary planes */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * vx->p[e]; + + /* See if this location close to, or outside boundary plane */ + if (v > -s->surftol) { + vx->sp[vx->nsp++] = vp; + vx->pmask |= (1 << i); + if (vx->nsp > di+1) + error("Assert in ofps det_vtx_gsurf : nsp %d > di+1 %d",vx->nsp, di+1); + } + } +#ifdef MAXINDEP_2D + if (s->sc[vx->pmask].valid == 0) { + vx->pmask = 0; + vx->nsp = 0; + } +#endif + +//printf("~1 vertex pmask = 0x%x, cmask = 0x%x\n",vx->pmask,vx->cmask); +} + +/* Given a device position and a list of surface planes, */ +/* move the position to lie on the closest location */ +/* on those planes. */ +static void confineto_gsurf(ofps *s, double *p, pleq **psp, int nsp) { + + if (nsp > 0) { /* It's a surface point, so keep it on the surface */ + int i, j, e, di = s->di; + double nn, np, q; + + /* Special case the common situation for speed. */ + if (nsp == 1) { + pleq *sp = psp[0]; + + /* Compute the dot product of the plane equation normal */ + for (nn = 0.0, e = 0; e < di; e++) + nn += sp->pe[e] * sp->pe[e]; + + /* Compute the dot product of the plane equation and the point */ + for (np = 0.0, e = 0; e < di; e++) + np += sp->pe[e] * p[e]; + + /* Compute the parameter */ + q = (sp->pe[di] + np)/nn; + + /* Compute the closest point */ + for (e = 0; e < di; e++) + p[e] -= q * sp->pe[e]; + + /* General case using matrix solution. */ + /* We compute the proportion of each plane normal vector to add to point */ + /* to map it onto all planes simultaniously (ie. to map the point to */ + /* the intersection of all the planes). */ + } else if (nsp > 1) { + double **ta, *TTA[MXPD + 1], TA[MXPD+1][MXPD + 1]; + double *tb, TB[MXPD + 1]; + + for (e = 0; e < nsp; e++) + TTA[e] = TA[e]; + ta = TTA; + tb = TB; + + /* For each combination of planes */ + for (i = 0; i < nsp; i++) { + pleq *spi = psp[i]; + for (j = i; j < nsp; j++) { + pleq *spj = psp[j]; + double vv; + + /* Compute dot product of the two normals */ + for (vv = 0.0, e = 0; e < di; e++) + vv += spi->pe[e] * spj->pe[e]; + ta[j][i] = ta[i][j] = vv; /* Use symetry too */ + } + + /* Compute right hand side */ + for (tb[i] = 0.0, e = 0; e < di; e++) + tb[i] += spi->pe[e] * p[e]; /* Dot prod of plane normal and point */ + tb[i] += spi->pe[di]; /* plus plane constant */ + } + /* Solve the simultaneous linear equations A.x = B */ + /* Return 1 if the matrix is singular, 0 if OK */ + if (solve_se(ta, tb, nsp) == 0) { + /* Compute the closest point */ + for (i = 0; i < nsp; i++) { + pleq *spi = psp[i]; + for (e = 0; e < di; e++) + p[e] -= tb[i] * spi->pe[e]; + } + } + } + /* The mapping may leave it out of gamut */ + ofps_clip_point2(s, p, p); + } +} + +/* Given a device position and a list of surface planes, */ +/* check that the point lies on all the planes. */ +/* Return NZ if it does, Z if it doesn't */ +static int checkon_gsurf(ofps *s, double *p, pleq **psp, int nsp) { + int i, e, di = s->di; + double nn, np, q; + + if (nsp == 0) + return 1; + + for (i = 0; i < nsp; i++) { + pleq *vp = psp[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * p[e]; + + /* See if this location close to, or outside boundary plane */ + if (fabs(v) > s->surftol) { + return 0; + } + } + return 1; +} + +/* Compute the estimated positioning error given two locations. */ +/* [ This seems to be the critical inner loop in regard to */ +/* overall speed. The dominant callers are dnsq_solver() 10%, */ +/* followed by add_node2voronoi() 5%, others <= 1% ] */ + +#ifdef NEVER /* Allow performance trace on eperr usage */ +static double ofps_comp_eperr(ofps *s, double *pddist, double *v, double *p, double *nv, double *np); +static double ofps_comp_eperr1(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr2(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr3(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr4(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr5(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr6(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr7(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr8(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr9(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +#else /* Production code */ +#define ofps_comp_eperr1 ofps_comp_eperr +#define ofps_comp_eperr2 ofps_comp_eperr +#define ofps_comp_eperr3 ofps_comp_eperr +#define ofps_comp_eperr4 ofps_comp_eperr +#define ofps_comp_eperr5 ofps_comp_eperr +#define ofps_comp_eperr6 ofps_comp_eperr +#define ofps_comp_eperr7 ofps_comp_eperr +#define ofps_comp_eperr8 ofps_comp_eperr +#define ofps_comp_eperr9 ofps_comp_eperr +#endif + +static double ofps_comp_eperr( + ofps *s, + double *pddist, /* If not NULL, return the device distance */ + double *v, /* Device perceptual value */ + double *p, /* Device sample location to be evaluated */ + double *nv, /* Other perceptual value */ + double *np /* Other perceptual value */ +) { + int ii, e, f, di = s->di; + int isc; + double tt, ddist, pdist; + double eperr; + + /* Uncertaintly error computed from device and perceptual distance */ + +#ifndef NEVER /* unrolled code */ + +#if MXPD > 4 +# error "ofps.c: Need to expand switch code for MXPD > 4" +#endif + /* Unrole the loop */ + pdist = ddist = 0.0; + switch (di) { + case 4: + tt = (p[3] - np[3]); /* Device distance */ + ddist += tt * tt; + tt = (v[3] - nv[3]); /* Perceptual distance */ + pdist += tt * tt; + case 3: + tt = (p[2] - np[2]); /* Device distance */ + ddist += tt * tt; + tt = (v[2] - nv[2]); /* Perceptual distance */ + pdist += tt * tt; + case 2: + tt = (p[1] - np[1]); /* Device distance */ + ddist += tt * tt; + tt = (v[1] - nv[1]); /* Perceptual distance */ + pdist += tt * tt; + case 1: + tt = (p[0] - np[0]); /* Device distance */ + ddist += tt * tt; + tt = (v[0] - nv[0]); /* Perceptual distance */ + pdist += tt * tt; + } +#else + /* General code */ + for (pdist = ddist = 0.0, e = 0; e < di; e++) { + + /* Compute the device distance */ + tt = (p[e] - np[e]); + ddist += tt * tt; + + /* Compute the perceptual distance */ + tt = (v[e] - nv[e]); + pdist += tt * tt; + } +#endif + + if (pddist != NULL) + *pddist = ddist; + + ddist *= 100.0 * 100.0; + +//printf("~1 Device distance = %f, dev error = %f\n",ddist,s->devd_wght * ddist); + + ddist = sqrt(ddist); + pdist = sqrt(pdist); + eperr = s->devd_wght * ddist + s->perc_wght * pdist; + +//printf("~1 Percept distance = %f, perc error = %f\n",pdist,s->perc_wght * pdist); + return eperr; +} + +/* Compute the per node estimated position and interpolation errors */ +/* given a location and a list of up to di+1 neighborhood measurement nodes. */ +static void ofps_pn_eperr( + ofps *s, + double *ce, /* return the curvature/interpolation error for each node (may be NULL) */ + double *ee, /* return the uncertaintly error for each node */ + double *sv, /* Perceptual value if known, othewise NULL */ + double *sp, /* Device sample location to be evaluated */ + node **nds, /* Array of pointers to measurement nodes */ + int nnds /* Number of measurement nodes (>= 1) */ +) { + int ii, e, di = s->di; + node *np; + double _sv[MXPD]; /* Sample perceptual value */ + double iv[MXPD]; /* Interpolated perceptual value */ + + /* Lookup perceptual value at sample point location */ + if (sv == NULL) { + sv = _sv; + ofps_cc_percept(s, sv, sp); + } + + /* Uncertaintly error computed from device and perceptual distance */ + for (ii = 0; ii < nnds; ii++) { + ee[ii] = ofps_comp_eperr1(s, NULL, sv, sp, nds[ii]->v, nds[ii]->p); + } + + if (ce == NULL) + return; + + /* This could be made more efficient by only computing it for every */ + /* vertex during the initial seeding, and then on subsequent */ + /* passes only computing it once the re-seed/fixups are done. */ + if (s->curv_wght != 0.0) { /* Don't waste the time unless it's used */ + + /* Compute an error estimate that's related to curvature */ + for (ii = 0; ii < nnds; ii++) { + double cp[MXPD]; /* Midway points location */ + double civ[MXPD]; /* Midway points interpolated perceptual value */ + double cv[MXPD]; /* Midway points actual perceptual value */ + + /* Compute a point midway between the sample location and the node */ + for (e = 0; e < di; e++) { + cp[e] = 0.5 * (sp[e] + nds[ii]->p[e]); + civ[e] = 0.5 * (sv[e] + nds[ii]->v[e]); + } + + /* Look the actual perceptual value */ + /* (Computing this for each vertex consumes about 8% of execution time) */ + ofps_cc_percept(s, cv, cp); + + /* Compute the difference between the interpolated and actual perceptual values */ + for (ce[ii] = 0.0, e = 0; e < di; e++) { + double tt; + tt = civ[e] - cv[e]; + ce[ii] += tt * tt; + } + ce[ii] = s->curv_wght * sqrt(ce[ii]); + } + } else { + for (ii = 0; ii < nnds; ii++) + ce[ii] = 0.0; + } +} + +/* Compute the estimated position error given the results */ +/* of ofps_pn_eperr() */ +static double ofps_eperr2( + double *ee, /* Uncertainty error for each node */ + int nnds /* Number of measurement nodes */ +) { + double eperr; + int ii; + + for (eperr = 1e80, ii = 0; ii < nnds; ii++) { + if (ee[ii] < eperr) + eperr = ee[ii]; + } + +//printf("~1 ofps_eperr returning %f\n",eperr); + return eperr; +} + +/* Compute the estimated sampling error of a location given */ +/* the results of ofps_pn_eperr() */ +static double ofps_eserr2( + double *ce, /* The estimated curvature/interpolation error for each node */ + double *ee, /* Uncertainty error for each node */ + int nnds /* Number of measurement nodes */ +) { + int ii; + double eserr; + double mxce; + +#ifdef NEVER + /* We assume errors are inverse probabilities, */ + /* and sum inverse squares. */ + for (mxce = 0.0, eserr = 0.0, ii = 0; ii < nnds; ii++) { + double tt; + + tt = ee[ii] * ee[ii]; + if (tt > NUMTOL) + eserr += 1.0/tt; + else { /* One error is close to zero */ + eserr = 0.0; + break; + } + if (ce[ii] > mxce) + mxce = ce[ii]; + } + if (ii >= nnds) + eserr = 1.0/sqrt(eserr); + eserr += mxce; + +#else /* This seems best ? */ + /* Return nearest neighbor error metric for the moment, */ + /* with the ce being the maximum of the surrounders. */ + for (mxce = 0.0, eserr = 1e80, ii = 0; ii < nnds; ii++) { + if (ee[ii] < eserr) + eserr = ee[ii]; + if (ce[ii] > mxce) + mxce = ce[ii]; + } + eserr += mxce; +#endif + +//printf("~1 ofps_eserr returning %f\n",eserr); + return eserr; +} + +/* - - - - - - - - - -- - - - - - - - - - - - - - - - - - - - */ +/* Finding vertex location code using dnsqe() */ + +typedef struct { + double p[MXPD]; +} loc; + +/* Context for callback */ +struct _vopt_cx { + ofps *s; + node *nds[MXPD+1]; /* List of real nodes */ + int nn; /* Number of real nodes */ + int on; /* Index of odd node */ + + pleq *sp[MXPD+1]; /* List of touched gamut surface planes */ + int nsp; /* Number of touched gamut surface planes */ + + double srad; /* Search radius used */ + double stp[MXPD]; /* Starting point used */ + +#ifdef DUMP_FERR + /* Debug: */ + int debug; /* nz to trace search path */ + loc *clist; /* List of points sampled */ + int _nl; /* Allocated size */ + int nl; /* Number of points */ +#endif + +}; typedef struct _vopt_cx vopt_cx; + +/* calculate the functions at x[] */ +int dnsq_solver( /* Return < 0 on abort */ + void *fdata, /* Opaque data pointer */ + int n, /* Dimenstionality */ + double *x, /* Multivariate input values */ + double *fvec, /* Multivariate output values */ + int iflag /* Flag set to 0 to trigger debug output */ +) { + vopt_cx *cx = (vopt_cx *)fdata; + ofps *s = cx->s; + int k, e, di = s->di; + int nn_1 = cx->nn-1; + double sv[MXPD]; + double cee[MXPD+1], teperr; + +#ifdef DUMP_FERR + /* record the points we visited */ + if (cx->debug) { + if (cx->nl >= cx->_nl) { + cx->_nl = 2 * cx->_nl + 5; + if ((cx->clist = (loc *)realloc(cx->clist, sizeof(loc) * cx->_nl)) == NULL) + error("ofps: malloc failed on debug location array %d", cx->_nl); + } + for (e = 0; e < di; e++) + cx->clist[cx->nl].p[e] = x[e]; + cx->nl++; + } +#endif +//printf("~1 dnsq_solver got %d nodes and %d planes\n",cx->nn,cx->nsp); + + /* Get eperr at each real node */ + ofps_cc_percept(s, sv, x); /* We have to compute it */ + for (k = 0; k < cx->nn; k++) + cee[k] = ofps_comp_eperr2(s, NULL, sv, x, cx->nds[k]->v, cx->nds[k]->p); + +//fprintf(stderr,"~1 maxeperr = %f\n",cmax); + +//printf("~1 error ="); +//for (k = 0; k < cx->nn; k++) +// printf(" %f",cee[k]); +//printf("\n"); + + /* We need to create nn-1 output values from nn eperr's */ + /* cx->on is the odd one out. */ + /* Difference to average (best) */ + for (teperr = 0.0, k = 0; k < cx->nn; k++) + teperr += cee[k]; + teperr /= (double)cx->nn; + + for (k = e = 0; k < cx->nn; k++) { + if (k != cx->on) { + fvec[e++] = (teperr - cee[k]); + } + } + + /* Compute plane errors */ + for (k = 0; k < cx->nsp; k++) { + pleq *pl = cx->sp[k]; + double v; + + for (v = pl->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += pl->pe[e] * x[e]; + fvec[nn_1 + k] = FGPMUL * v; + } + + s->funccount++; + +//for (k = 0; k < nn_1; k++) +//printf("~1 fvec[%d] = %f\n",k,fvec[k]); +//printf("dnsq_solver returning %s from %s\n",ppos(di,fvec),ppos(di,x)); +//fprintf(stderr,"dnsq_solver returning %s from %s\n",ppos(di,fvec),ppos(di,x)); + + return 0; +} + +/* Locate a vertex position that has the eperr from all the real nodes */ +/* being equal. Set eperr, eserr and subjective value v[] too. */ +/* vv->ceperr contains the current eperr that must be bettered. */ +/* Return 0 if suceeded, 1 if best result is out of tollerance, 2 if failed. */ +static int position_vtx( + ofps *s, + nodecomb *vv, /* Return the location and its error */ + int startex, /* nz if current position is to be used as initial start point */ + int repos, /* nz after an itteration and we expect out of gamut */ + int fixup /* nz if doing fixups after itteration and expect out of gamut ??? */ +) { + int e, di = s->di; + int k, ii; + double tw; + double atp[MXPD], mct[MXPD]; /* Average node position, middle of closest 2 nodes */ + double osp[MXPD]; /* Original start position, start position */ + double cdist, fdist; /* Closest/furthest two points distance apart */ + double bsrad; /* Basic search radius, search radius */ + double ftol = FTOL; /* Final eperr tolerance */ + int tfev = 0, tcalls = 0; /* Track average successful fevs */ + int maxfev = 50; /* Maximum function evaluations */ + int tries, notries = MAXTRIES; /* Point to give up on. Giving up will error. */ + vopt_cx cx, pcx; /* dnsq context + previous context */ + +#ifdef DEBUG + printf("Position_vtx called for comb %s\n",pcomb(di,vv->nix)); +#endif + + s->positions++; + s->sob->reset(s->sob); + +#ifdef DUMP_FERR + cx.debug = 0; +#endif + + /* Setup for dnsq to optimize for equal eperr */ + cx.s = s; + + /* Pointers to real nodes. Although we allow for the */ + /* fake inner/outer nodes, eperr() will fail them later. */ + for (ii = e = 0; e <= di; e++) { + if (vv->nix[e] >= 0 || vv->nix[e] < -s->nbp) + cx.nds[ii++] = s->n[vv->nix[e]]; + } + cx.nn = ii; + + if (ii == 0) { + fflush(stdout); + error("ofps: unexpectedely got no real nodes in vertex position %s",pcomb(di,vv->nix)); + } + +#ifdef DEBUG + printf("%d real nodes\n",ii); + for (k = 0; k < ii; k++) + printf("Node ix %d at %s\n",cx.nds[k]->ix,ppos(di,cx.nds[k]->p)); +#endif + + /* Setup gamut suface planes */ + cx.nsp = 0; + if (ii < (di+1)) { + /* Create list of pointers to the gamut surface planes involved */ + for (e = 0, k = ii; k <= di; e++, k++) { +#ifdef DEBUG + printf("Adding plane for node ix %d\n",vv->nix[k]); +#endif + cx.sp[e] = &s->gpeqs[-1 - vv->nix[k]]; + } + cx.nsp = e; + } + + /* If there is only one node, map it to the planes */ + /* and we're done. */ + if (ii == 1) { + double ee[MXPD+1]; + + for (e = 0; e < di; e++) + vv->p[e] = cx.nds[0]->p[e]; + + confineto_gsurf(s, vv->p, cx.sp, cx.nsp); + + if (checkon_gsurf(s, vv->p, cx.sp, cx.nsp) == 0) { +#ifdef DEBUG + printf("Single node comb %s failed to confine to gamut surface\n",pcomb(di,vv->nix)); +#endif + return 2; + } + + /* Compute perceptual (can't clip because of confine) */ + s->percept(s->od, vv->v, vv->p); + + /* Compute the eperr's for each node. */ + ofps_pn_eperr(s, vv->ce, ee, vv->v, vv->p, cx.nds, cx.nn); + + /* Compute errors at returned location */ + vv->eperr = ofps_eperr2(ee, cx.nn); + vv->eserr = ofps_eserr2(vv->ce, ee, cx.nn); + +#ifdef DEBUG + printf("Single node, returning comb %s opt pos = %s, eperr = %f, eserr = %f\n",pcomb(di,vv->nix),ppos(di,vv->p),vv->eperr,vv->eserr); +#endif + return 0; + } + { + /* Compute average of real nodes */ + for (e = 0; e < di; e++) + atp[e] = 0.0; + for (tw = 0.0, k = 0; k < ii; k++) { + double w = 1.0; + + for (e = 0; e < di; e++) + atp[e] += cx.nds[k]->p[e]; + tw += w; + } + for (e = 0; e < di; e++) + atp[e] /= tw; + +#ifdef DEBUG + printf("Average of %d real node start pos = %s\n",ii,ppos(di,atp)); +#endif + } + + /* Locate the closest and furthest two nodes */ + { + double ceperr = 1e200; + int i, j, bi = 0, bj = 0; + + /* Find the two vectors that have the closest eperr. Brute force search */ + /* and track the device position for the two points involved. */ + /* Also locate the smallest device distance. */ + cdist = 1e200; + fdist = -1.0; + for (i = 0; i < (ii-1); i++) { + for (j = i+1; j < ii; j++) { + double dist; + dist = ofps_comp_eperr3(s, NULL, cx.nds[i]->v, cx.nds[i]->p, cx.nds[j]->v, cx.nds[j]->p); + if (dist < ceperr) { + ceperr = dist; + bi = i; + bj = j; + } + for (dist = 0.0, e = 0; e < di; e++) { + double tt = cx.nds[i]->p[e] - cx.nds[j]->p[e]; + dist += tt * tt; + } + if (dist < cdist) + cdist = dist; + if (dist > fdist) + fdist = dist; + } + } + + fdist = sqrt(fdist); + cdist = sqrt(cdist); + + /* Compute the middle of the two closest eperr nodes */ + for (e = 0; e < di; e++) + mct[e] = 0.5 * (cx.nds[bi]->p[e] + cx.nds[bj]->p[e]); + + /* Set a step/search radius based on the distance */ + /* between the two closest device distance nodes. */ + if (cdist < COINTOL) { +#ifdef DEBUG + printf("Two nodes are cooincident! - dnsq will fail!\n"); +#endif + if (s->verb > 1) + warning("Two nodes are cooincident! ix %d, pos %s and ix %d pos %s",cx.nds[bi]->ix,ppos(di,cx.nds[bi]->p),cx.nds[bj]->ix,ppos(di,cx.nds[bj]->p)); + } + bsrad = 0.2 * cdist; + if (bsrad < 1e-5) + bsrad = 1e-5; + } + + /* Set initial starting position */ +// if (startex && ! ofps_would_clip_point(s, vv->p)) { } + if (startex) { + + for (e = 0; e < di; e++) + osp[e] = vv->p[e]; + +// ofps_clip_point(s, vv->p, vv->p); +#ifdef DEBUG + printf("Startex startposition = %s\n",ppos(di,atp)); +#endif + } else { + double mwt = 0.3; + /* Start at equalateral point between two closest */ + /* nodes towards average. */ + for (e = 0; e < di; e++) { +// osp[e] = mct[e]; /* Best for 2D ? */ +// osp[e] = atp[e]; /* best for 3D/4D ? */ + osp[e] = mwt * mct[e] + (1.0 - mwt) * atp[e]; /* Good compromize */ + } + } + + /* Try our computed starting position first, and if that fails, */ + /* retry with a random offset starting location. */ + cx.srad = bsrad; + for (e = 0; e < di; e++) + cx.stp[e] = osp[e]; + cx.on = 0; + + for (tries = 0; tries < notries; tries++) { + int rv; + double fvec[MXPD]; /* Return function value at solution */ + int cfunccount; + + if (tries > 0) { /* Determine a starting point */ + + /* Try all possible odd one outs */ + cx.on++; + + /* On carry, use a random start offset */ + /* (Tried culling random starts and odd one outs */ + /* by picking one with a low norm, but */ + /* while this reduced the number of small */ + /* retries, it worsened the number of failures */ + /* and sucesses after a large number of retries.) */ + if (cx.on >= cx.nn) { + double rscale = 1.0; /* Random scale */ + double fval[MXPD]; + int nc; + + s->sob->next(s->sob, cx.stp); + + /* Scale random value around original starting point */ + for (e = 0; e < di; e++) { + cx.stp[e] = cx.stp[e] * 2.0 - 1.0; /* Make -1.0 to 1.0 range */ + if (cx.stp[e] < 0.0) { + cx.stp[e] *= rscale * (osp[e] - s->imin[e]); + } else { + cx.stp[e] *= rscale * (s->imax[e] - osp[e]); + } + cx.stp[e] += atp[e]; + } + ofps_clip_point4(s, cx.stp, cx.stp); + cx.on = 0; + } + } + + /* Set start position */ + for (e = 0; e < di; e++) + vv->p[e] = cx.stp[e]; + +#ifdef DEBUG + printf("Starting location = %s, srad = %f, on = %d\n",ppos(di,cx.stp),cx.srad,cx.on); +#endif + +//printf("\nStarting location = %s, srad = %f\n",ppos(di,cx.stp),cx.srad); + /* Locate vertex */ + cfunccount = s->funccount; + s->dnsqs++; + if (tcalls == 0) + maxfev = 500; + else + maxfev = 2 * tfev/tcalls; + rv = dnsqe((void *)&cx, dnsq_solver, NULL, di, vv->p, cx.srad, fvec, 0.0, ftol, maxfev, 0); + if ((s->funccount - cfunccount) > 20) { +//printf("More than 20: %d\n",s->funccount - cfunccount); + } + if ((s->funccount - cfunccount) > s->maxfunc) { + s->maxfunc = (s->funccount - cfunccount); +//printf("New maximum %d\n",s->maxfunc); + } + + if (rv != 1 && rv != 3) { + /* Fail to converge */ +#ifdef DEBUG + printf("dnsqe fail to converge, retuned %d\n",rv); +#endif + } else { + double ee[MXPD+1]; + double max, min; + double ple = 0.0; /* Gamut plane error */ + + /* Evaluate the result */ + + /* Update average function evaluations */ + tcalls++; + tfev += s->funccount - cfunccount; + +#ifdef DEBUG + printf("dnsq pos %s\n",ppos(di,vv->p)); + if (rv == 3) + printf("dnsq returned 3 - dtol too small\n"); +#endif + + /* Compute perceptual */ + ofps_cc_percept(s, vv->v, vv->p); + + /* Compute the eperr's for each node. */ + ofps_pn_eperr(s, vv->ce, ee, vv->v, vv->p, cx.nds, cx.nn); + + min = 1e80, max = -1e80; + for (e = 0; e < cx.nn; e++) { + if (min > ee[e]) + min = ee[e]; + if (max < ee[e]) + max = ee[e]; + } + + /* Compute the worst plane equation error */ + for (k = 0; k < cx.nsp; k++) { + double v; + for (v = cx.sp[k]->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += cx.sp[k]->pe[e] * vv->p[e]; + v = fabs(v * FGPMUL); +//printf("~1 gamut plane %d err = %f\n",k,v); + if (v > ple) + ple = v; + } + +#ifdef DEBUG + printf("new vertex pos %s has eperrs match by %f & gamut plane %f\n",ppos(di,vv->p),max-min,ple); +#endif + /* If not dtol too large, Check that the balance is acceptable */ + if (/* rv != 3 && */ + (((cx.nn > 1) && (max - min) > (ftol * 2.0)) + || ((cx.nsp > 0) && ple > (ftol * 2.0)))) { + /* Don't use this */ +#ifdef DEBUG + printf("new vertex pos %s doesn't have sufficient matching eperrs and on gamut plane\n",ppos(di,vv->p),max-min,ple); +#endif + } else { + /* eperr balance is acceptable, so further */ + /* evaluate the location found. */ + double ss; + + s->sucfunc += (s->funccount - cfunccount); + s->sucdnsq++; + + /* Compute how much the result is out of gamut */ + vv->oog = ofps_oog(s, vv->p); +#ifdef DEBUG + if (vv->oog > 0.01) + printf("dnsq returned out of gamut result by %e\n", vv->oog); +#endif + + /* Compute errors at returned location */ + vv->eperr = ofps_eperr2(ee, cx.nn); + vv->eserr = ofps_eserr2(vv->ce, ee, cx.nn); + + /* Decide whether a vertex location is acceptable */ + /* We accept a point that has an acceptable error balance */ + /* and improves the eperr, and is in gamut if this is not a repos. */ + /* (There's some mystery stuff in here for fixups) */ + if ((cx.nn <= 1) || ((max - min) <= (ftol * 2.0) + && ((!repos && vv->oog <= 0.01 && vv->eperr < (vv->ceperr + 0.1)) + || ( repos && vv->oog <= 0.01 && vv->eperr < (5.0 * vv->ceperr + 20.0)) + || ( repos && vv->oog > 0.0 && vv->eperr < 1000.0) + || ( fixup && vv->oog < 20.0 && vv->eperr < (vv->ceperr + 0.01)) + ))) { + + if (tries > s->maxretries) + s->maxretries = tries; +#ifdef DEBUG + printf(" - comb %s suceeded on retry %d (max %d)\n",pcomb(di,vv->nix),tries,s->maxretries); + printf(" oog = %f, eperr = %f, ceperr = %f\n",vv->oog,vv->eperr,vv->ceperr); +#endif +//if (tries > 10) +// printf(" - comb %s suceeded on retry %d (max %d)\n",pcomb(di,vv->nix),tries,s->maxretries); +// +//printf("Solution for comb %s has eperr %f < ceperr %f and not out of gamut by %f, retry %d\n",pcomb(di,vv->nix),vv->eperr,vv->ceperr,vv->oog,tries+1); +//printf("Solution is at %s (%s)\n",ppos(di,vv->p),ppos(di,vv->v)); + +// +//if (repos) printf("~1 vtx no %d dtav = %f, fdist = %f\n",vv->dtav,fdist); +//if (repos && vv->vv->no == 889) printf("~1 vtx no %d dtav = %f, fdist = %f\n",vv->vv->no,vv->dtav,fdist); + +#ifdef DUMP_FERR /* Create .tiff of dnsq function error */ + if (tries >= DUMP_FERR) { + printf("Suceeded on retry %d, dumping debug rasters\n",tries); + + /* Re-run the last unsucessful dnsq, to trace the path */ + pcx.debug = 1; + pcx.clist = NULL; + pcx._nl = 0; + pcx.nl = 0; + dnsqe((void *)&pcx, dnsq_solver, NULL, di, pcx.stp, pcx.srad, fvec, 0.0, ftol, maxfev, 0); + pcx.debug = 0; + dump_dnsqe(s, "dnsq_fail2.tif", vv->nix, &pcx); + free(pcx.clist); + + /* Re-run the first unsucessful dnsq, to trace the path */ + pcx.debug = 1; + pcx.clist = NULL; + pcx._nl = 0; + pcx.nl = 0; + pcx.on = 0; /* First odd one out */ + for (e = 0; e < di; e++) + pcx.stp[e] = atp[e]; /* best start ? */ + dnsqe((void *)&pcx, dnsq_solver, NULL, di, pcx.stp, pcx.srad, fvec, 0.0, ftol, maxfev, 0); + pcx.debug = 0; + dump_dnsqe(s, "dnsq_fail1.tif", vv->nix, &pcx); + free(pcx.clist); + + /* Re-run the sucessful dnsq, to trace the path */ + cx.debug = 1; + cx.clist = NULL; + cx._nl = 0; + cx.nl = 0; + dnsqe((void *)&cx, dnsq_solver, NULL, di, cx.stp, cx.srad, fvec, 0.0, ftol, maxfev, 0); + cx.debug = 0; + dump_dnsqe(s, "dnsq_suc.tif", vv->nix, &cx); + free(cx.clist); + exit(0); + } +#endif + + break; /* Use the result now */ + } +#ifdef DEBUG + printf("Solution for comb %s has eperr %f > ceperr %f or out of gamut by %f, retry %d\n",pcomb(di,vv->nix),vv->eperr,vv->ceperr,vv->oog,tries+1); +#endif + } + } + pcx = cx; /* Save unsucessful context for debug */ + } /* Retry */ + + /* If we've run out of tries, return the best solution we found */ + if (tries >= notries) { + + /* Show up if this ever gets used */ + for (e = 0; e < di; e++) + vv->p[e] = -0.1; + ofps_cc_percept(s, vv->v, vv->p); +#ifdef DEBUG + printf("vertex location solving failed after %d tries\n",tries); +#endif + if (s->verb > 1) + warning("vertex location solving failed after %d tries",tries); + return 2; /* Don't use this vertex */ + } + +#ifdef DEBUG + printf("Returning comb %s opt pos = %s val = %s, eperr = %f, eserr = %f\n",pcomb(di,vv->nix),ppos(di,vv->p),ppos(di,vv->v),vv->eperr,vv->eserr); +#endif + + return 0; +} + +/* --------------------------------------------------------- */ +/* Deal with creating a dummy vertex to represent one that */ +/* can't be positioned. We simply locate the best point we can. */ + +/* calculate the functions at x[] */ +double powell_solver( /* Return < 0 on abort */ + void *fdata, /* Opaque data pointer */ + double *x /* Multivariate input values */ +) { + vopt_cx *cx = (vopt_cx *)fdata; + ofps *s = cx->s; + int k, e, di = s->di; + int nn_1 = cx->nn-1; + double sv[MXPD]; + double cee[MXPD+1], teperr; + double ss, oog; + double rv = 0.0; + +//printf("~1 powell_solver got %d nodes and %d planes\n",cx->nn,cx->nsp); + + /* Get eperr at each real node */ + ofps_cc_percept(s, sv, x); /* We have to compute it */ + for (k = 0; k < cx->nn; k++) + cee[k] = ofps_comp_eperr2(s, NULL, sv, x, cx->nds[k]->v, cx->nds[k]->p); + +//fprintf(stderr,"~1 maxeperr = %f\n",cmax); + +//printf("~1 eprror ="); +//for (k = 0; k < cx->nn; k++) +// printf(" %f",cee[k]); +//printf("\n"); + + /* The error is zero if the input value */ + /* is within gamut, all the real node eperr's are */ + /* the same, and the ditance to gamut planes is zero. */ + + /* Compute average eperr */ + for (teperr = 0.0, k = 0; k < cx->nn; k++) + teperr += cee[k]; + teperr /= (double)cx->nn; + +//printf("~1 average = %f\n", teperr); + + /* Add diference to average */ + for (k = e = 0; k < cx->nn; k++) { + if (k != cx->on) { + double tt; + tt = teperr - cee[k]; + rv += tt * tt; + } + } +//printf("~1 after diff to avg rv = %f\n", rv); + + /* Compute distances to planes */ + for (k = 0; k < cx->nsp; k++) { + pleq *pl = cx->sp[k]; + double v; + + for (v = pl->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += pl->pe[e] * x[e]; + v *= FGPMUL; + rv += v * v; + } +//printf("~1 after diff to planes rv = %f\n", rv); + + /* Compute distance out of gamut */ + + for (ss = oog = 0.0, e = 0; e < di; e++) { + if (x[e] < (s->imin[e])) { + double tt = s->imin[e] - x[e]; + if (tt > oog) oog = tt; + } else if (x[e] > (s->imax[e])) { + double tt = x[e] - s->imax[e]; + if (tt > oog) oog = tt; + } + ss += x[e]; + } + if (ss > s->ilimit) { + double tt; + ss = (ss - s->ilimit)/di; /* Axis aligned distance to ink limit */ + tt = sqrt((double)di) * ss; /* Diagonal distance to ink limit */ + if (tt > oog) + oog = tt; + } + + rv += 1000.0 * oog * oog; +//printf("~1 after oog rv = %f\n", rv); + +//printf("powell_solver returning %f from %s\n",rv, ppos(di,x)); +//fprintf(stderr,"powell_solver returning %f from %s\n",rv, ppos(di,x)); + + return 0; +} + +/* Fake up a vertex position when position_vtx() has failed. */ +static void dummy_vtx_position( + ofps *s, + vtx *ev1, vtx *ev2, /* Deleted and non-deleted vertexes */ + nodecomb *vv /* Return the location and its error */ +) { + int e, di = s->di; + int k, ii; + vopt_cx cx; /* dnsq context */ + double ss[MXPD]; + double bl; /* Location on path between del and !del vertexes */ + double ee[MXPD+1]; + +#ifdef DEBUG + printf("dummy_vtx_position called for comb %s\n",pcomb(di,vv->nix)); +#endif + +#ifdef DUMP_FERR + cx.debug = 0; +#endif + + /* Setup for dnsq to optimize for equal eperr */ + cx.s = s; + + /* Pointers to real nodes. Although we allow for the */ + /* fake inner/outer nodes, eperr() will fail them later. */ + for (ii = e = 0; e <= di; e++) { + if (vv->nix[e] >= 0 || vv->nix[e] < -s->nbp) + cx.nds[ii++] = s->n[vv->nix[e]]; + } + cx.nn = ii; + + if (ii == 0) { + fflush(stdout); + error("ofps: unexpectedely got no real nodes in vertex position %s",pcomb(di,vv->nix)); + } + +#ifdef DEBUG + printf("%d real nodes\n",ii); + for (k = 0; k < ii; k++) + printf("Node ix %d at %s (%s)\n",cx.nds[k]->ix,ppos(di,cx.nds[k]->p),ppos(di,cx.nds[k]->v)); +#endif + + /* Setup gamut suface planes */ + cx.nsp = 0; + if (ii < (di+1)) { + /* Create list of pointers to the gamut surface planes involved */ + for (e = 0, k = ii; k <= di; e++, k++) { +#ifdef DEBUG + printf("Adding plane for node ix %d\n",vv->nix[k]); +#endif + cx.sp[e] = &s->gpeqs[-1 - vv->nix[k]]; + } + cx.nsp = e; + } + + /* Set search area */ + for (e = 0; e < di; e++) + ss[e] = 0.001; + + /* Compute a position on the locus between the del and !del vertexes */ + bl = (ev1->nba_eperr - ev2->eperr)/(ev1->eperr - ev2->eperr); + if (bl < 0.0) + bl = 0.0; + else if (bl > 1.0) + bl = 1.0; + for (e = 0; e < di; e++) { + vv->p[e] = bl * vv->v1[0]->p[e] + (1.0 - bl) * vv->v2[0]->p[e]; + } + + ofps_clip_point5(s, vv->p, vv->p); + +#ifndef NEVER + /* Seem to often fail due to pathalogical condition for max type eperr() */ + if (powell(NULL, di, vv->p, ss, 1e-5, 1000, powell_solver, &cx, NULL, NULL)) { + warning("dummy_vtx_position powell failed"); + } +#endif + + ofps_clip_point5(s, vv->p, vv->p); + + /* Compute perceptual (was clipped above) */ + s->percept(s->od, vv->v, vv->p); + + /* Compute the eperr's for each node. */ + ofps_pn_eperr(s, vv->ce, ee, vv->v, vv->p, cx.nds, cx.nn); + + /* Compute errors at returned location */ + vv->eperr = ofps_eperr2(ee, cx.nn); + vv->eserr = ofps_eserr2(vv->ce, ee, cx.nn); + +#ifdef DEBUG + printf("Returning comb %s opt pos = %s val = %s, eperr = %f, eserr = %f\n",pcomb(di,vv->nix),ppos(di,vv->p),ppos(di,vv->v),vv->eperr,vv->eserr); +#endif +} + +/* --------------------------------------------------- */ +/* Vertex add routines */ + +/* Comlete adding a node to a Voronoi surface. */ +/* It's assumed that the hit nodes have been added to the s->nxh list */ +/* and the s->nvcheckhits set to the number of hit vertexes. */ +/* The nodes may be fake gamut boundary nodes, but must have */ +/* real vertexes. Vertexes that are marked for deletion or new ones */ +/* will be added to the batch update list for later execution. */ +/* Nodes that have had vertexes added to them will added to the node 'to be updated' list */ +/* and then a batch update will be executed. */ +/* Return 0 if it wasn't added, */ +/* Return 1 if it was added */ +static int add_to_vsurf( +ofps *s, +node *nn, /* Node to add */ +int fixup, /* 0 = seed, 1 = fixup ?? */ +int abortonfail /* 0 = ignore position failures, 1 = abort add if there are any failures */ +) { + int e, ff, f, di = s->di; + int i, j, k, ndi; + vtx *tev; + vtx *ev1, *ev2; /* Deleted and non-deleted vertexes */ + int ndelvtx; /* Number of vertexes to delete */ + int nncombs; /* Number of node combinations generated, allocated. */ + +#ifdef DEBUG + printf("\nAdd_to_vsurf node ix %d (p %s), i_sm %s, a_sm %s\n",nn->ix, ppos(di,nn->p),psm(s,&s->sc[nn->pmask].i_sm),psm(s,&s->sc[nn->pmask].a_sm)); +#endif + +#ifdef DEBUG + if (nn->ix < -s->nbp) { + printf("Fake node involved\n"); + } +#endif + + /* Update stats for the hit vertexes */ + s->nsurfadds++; + s->nhitv += s->nvcheckhits; + if (s->nvcheckhits > s->maxhitv) + s->maxhitv = s->nvcheckhits; + + if (s->nvcheckhits == 0) { /* Node doesn't improve any vertex eperrs */ +#ifdef DEBUG + printf("Add_to_vsurf done - not better, not added\n"); +#endif + return 0; + } + +#ifdef DEBUG + printf("There are %d vertexes to replace, and %d potential replacement vertexes\n",s->nvcheckhits, di * s->nvcheckhits); + printf("Vertexes marked for deletion are:\n"); + for (ev1 = s->nxh; ev1 != NULL; ev1 = ev1->nxh) + printf(" vtx no %d nix %s\n",ev1->no,pcomb(di,ev1->nix)); +#endif + + /* Generate all the potential new node combinations/replacement verticies. */ + /* We check each deleted vertex against its non-deleted neighbours. */ + /* The same replacement combination may be generated more than once. */ + for (nncombs = 0, ev1 = s->nxh; ev1 != NULL; ev1 = ev1->nxh) { + + for (ndi = 0; ndi < ev1->nnv; ndi++) { + int nix[MXNIX]; +#ifdef INDEP_SURFACE + setmask cvm; /* visibility setmask for each vertex combination */ +#endif + ev2 = ev1->nv[ndi]; + + if (ev2->del != 0) + continue; /* Can't pair with another deleted vertex */ + +#ifdef DEBUG + printf("\nDealing with vertex pair del no %d nix %s, and !del no %d nix %s\n",ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); +#endif + +#ifdef DEBUG + { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = ev1->nix[MXPD+2]; /* nixm */ + bb = ev2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) { + error("Vertexes %d comb %s and %d comb %s are vn neighbours that shouldn't be!", ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); + } + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (ev1->nix[e] == ev2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (ev1->nix[e] > ev2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) { + error("Vertexes %d comb %s and %d comb %s are vn neighbours that shouldn't be!", ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); + } + } +#endif /* DEBUG */ + /* Create the node combination */ + for (e = 0; e <= di; e++) { + nix[e] = ev1->nix[e]; + for (f = 0; f <= di; f++) { + if (nix[e] == ev2->nix[f]) + break; + } + if (f > di) /* Found one different */ + nix[e] = nn->ix; + } + sort_nix(s, nix); + + /* Check that the same node doesn't appear twice */ + /* (~~99 Why do we need this - does it ever happen ??) */ + for (e = 0; e < di; e++) { + for (k = e+1; k <= di; k++) { + if (nix[e] == nix[k]) { +#ifdef DEBUG + printf("New vertex with duplicate nodes %s from vertexes %d comb %s and %d comb %s ignored\n",pcomb(di,nix),ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); + if (s->verb > 1) + warning("New vertex with duplicate nodes %s from vertexes %d comb %s and %d comb %s ignored",pcomb(di,nix),ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); +#endif + break; + } + } + if (k <= di) + break; + } + if (e < di) + continue; + + /* See if the combination has at least one real node, */ + /* and none of the inner or outer fake nodes. */ + /* (~~99 Do we need this - does it ever happen ??) */ + k = 0; + for (e = 0; e <= di; e++) { + if (nix[e] >= 0) + k |= 1; /* Found one real node */ + else if (nix[e] < -s->nbp) + break; /* There's a fake inner or outer node though */ + } + if (e <= di || k == 0) { +#ifdef DEBUG + printf("Combination ix: %s, skipped because it has no real nodes\n",pcomb(di,nix)); + if (s->verb > 1) + warning("Combination ix: %s, skipped because it has no real nodes",pcomb(di,nix)); +#endif + continue; + } + +#ifdef INDEP_SURFACE + /* Compute the pertinent visibility mask for this vertex creation. */ + /* Note that we keep pairs of vertexes that aren't visible to each other */ + /* so that we can add them to the vertex net. */ + sm_andand(s, &cvm, &ev1->vm, &ev2->vm, &s->sc[comp_cmask(s, nix)].a_sm); +#ifdef DEBUG + printf("Combination ix: %s, vm %s, eperrs %f to %f\n",pcomb(di,nix),psm(s,&cvm),ev1->eperr,ev2->eperr); +#endif +#else /* !INDEP_SURFACE */ +#ifdef DEBUG + printf("Combination ix: %s, eperrs %f to %f\n",pcomb(di,nix),ev1->eperr,ev2->eperr); +#endif +#endif /* !INDEP_SURFACE */ + + /* See if this combination is already in the list */ + /* due to a pair having the same common nodes. */ + for (k = 0; k < nncombs; k++) { + + if (s->combs[k].nix[MXPD+1] != nix[MXPD+1]) /* Hashes don't match */ + continue; + + for (e = 0; e <= di; e++) { /* Do full check */ + if (s->combs[k].nix[e] != nix[e]) { + break; /* No match */ + } + } + if (e > di) { /* Match */ + double ceperr; + if (s->combs[k].count >= s->combs[k]._count) { + s->combs[k]._count = 2 * s->combs[k]._count + 5; + if ((s->combs[k].v1 = (vtx **)realloc(s->combs[k].v1, + sizeof(vtx *) * s->combs[k]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[k]._count); + if ((s->combs[k].v2 = (vtx **)realloc(s->combs[k].v2, + sizeof(vtx *) * s->combs[k]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[k]._count); + } + s->combs[k].v1[s->combs[k].count] = ev1; + s->combs[k].v2[s->combs[k].count] = ev2; + s->combs[k].count++; + + /* Update ceperr if this is higher */ + ceperr = ev1->eperr > ev2->eperr ? ev1->eperr : ev2->eperr; + if (ceperr > s->combs[k].ceperr) + s->combs[k].ceperr = ceperr; +#ifdef DEBUG + printf("Vertex generation count now %d with ceperr %f\n",s->combs[k].count,s->combs[k].ceperr); +#endif +#ifdef INDEP_SURFACE + sm_or(s, &s->combs[k].vm, &s->combs[k].vm, &cvm); +#ifdef DEBUG + printf("Vertex combination vm now %s\n",psm(s,&s->combs[k].vm)); +#endif +#endif + break; + } + } + if (k < nncombs) + continue; /* Already on list */ + + /* Add this combination to the list as a new entry */ + if (nncombs >= s->_ncombs) { + int o_ncombs = s->_ncombs; + s->_ncombs = 2 * s->_ncombs + 5; + if ((s->combs = (nodecomb *)realloc(s->combs, sizeof(nodecomb) * s->_ncombs)) == NULL) + error ("ofps: malloc failed on node combination array length %d", s->_ncombs); + memset((void *)(s->combs + o_ncombs), 0, + (s->_ncombs - o_ncombs) * sizeof(nodecomb)); + } + + if (1 >= s->combs[nncombs]._count) { + s->combs[nncombs]._count = 2 * s->combs[nncombs]._count + 5; + if ((s->combs[nncombs].v1 = (vtx **)realloc(s->combs[nncombs].v1, + sizeof(vtx *) * s->combs[nncombs]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[nncombs]._count); + if ((s->combs[nncombs].v2 = (vtx **)realloc(s->combs[nncombs].v2, + sizeof(vtx *) * s->combs[nncombs]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[nncombs]._count); + } + s->combs[nncombs].v1[0] = ev1; + s->combs[nncombs].v2[0] = ev2; + s->combs[nncombs].count = 1; + for (e = 0; e <= di; e++) + s->combs[nncombs].nix[e] = nix[e]; + s->combs[nncombs].nix[MXPD+1] = nix[MXPD+1]; /* Copy Hash */ + s->combs[nncombs].nix[MXPD+2] = nix[MXPD+2]; /* Copy nixm */ + s->combs[nncombs].ceperr = ev1->eperr > ev2->eperr ? ev1->eperr : ev2->eperr; + s->combs[nncombs].startex = 0; + s->combs[nncombs].pvalid = 0; + s->combs[nncombs].vv = NULL; +#ifdef INDEP_SURFACE + sm_cp(s, &s->combs[nncombs].vm, &cvm); +#else /* !INDEP_SURFACE */ + sm_set(s, &s->combs[nncombs].vm, 0); /* Not used */ +#endif /* !INDEP_SURFACE */ + + nncombs++; + +#ifdef DEBUG + printf("Adding combination to list with ceperr %f, vm %s, list size %d\n",s->combs[nncombs-1].ceperr,psm(s,&s->combs[nncombs-1].vm), nncombs); +#endif + } + } + +#ifdef DEBUG + printf("\nThere are %d unique node combinations in list, locating combs. in list:\n",nncombs); +#endif + + /* Locate the replacement vertex positions */ + for (i = 0; i < nncombs; i++) { + + ev1 = s->combs[i].v1[0]; + ev2 = s->combs[i].v2[0]; + +#ifdef INDEP_SURFACE + /* Ignore pairs of vertexes that don't form a visible new combination */ + if (sm_test(s, &s->combs[i].vm) == 0) { +#ifdef DEBUG + printf("Combination ix: %s, skipped because vm %s == 0x0\n",pcomb(di,s->combs[i].nix),psm(s,&s->combs[i].vm)); +#endif + continue; + } +#endif /* INDEP_SURFACE */ + +#ifdef DEBUG + printf("\nNode combination ix: %s\n",pcomb(di,s->combs[i].nix)); +#endif + /* Try and locate existing vertex that is due to the same nodes */ + if ((s->combs[i].vv = vtx_cache_get(s, s->combs[i].nix)) != NULL) { +#ifdef DEBUG + printf("Vertex is same as existing no %d\n",s->combs[i].vv->no); +#endif + + s->combs[i].vv->add = 2; /* Updated vertex */ + + /* If a new vertex is not the same as the two nodes it's being created */ + /* from yet has been marked for deletion, reprieve it. */ + if (s->combs[i].vv->del) { + + s->combs[i].vv->del = 0; +#ifdef DEBUG + printf("New existing vertex no %d is deleted vertex - reprieve it\n",s->combs[i].vv->no); +#endif + } + } + + /* We need to create a replacement vertex, locate position for it */ + if (s->combs[i].vv == NULL) { +#ifdef DEBUG + printf("About to locate comb ix: %s, ceperr %f\n",pcomb(di,s->combs[i].nix),s->combs[i].ceperr); +#endif +//printf("~1 About to locate comb ix: %s, ceperr %f\n",pcomb(di,s->combs[i].nix),s->combs[i].ceperr); + /* Compute a starting position between the deleted/not deleted pair */ + /* This seems very slightly better than the default mct[] + atp[] scheme. */ + if (nn->ix >= 0) { /* If not boundary */ + double bl; + bl = (ev1->nba_eperr - ev2->eperr)/(ev1->eperr - ev2->eperr); + if (bl < 0.0) + bl = 0.0; + else if (bl > 1.0) + bl = 1.0; + for (e = 0; e < di; e++) { + s->combs[i].p[e] = bl * s->combs[i].v1[0]->p[e] + (1.0 - bl) * s->combs[i].v2[0]->p[e]; + } + ofps_clip_point5(s, s->combs[i].p, s->combs[i].p); +//printf("Startex is %s\n",ppos(di,s->combs[i].p)); + s->combs[i].startex = 1; + } + /* find vertex position of max eperr */ + if (position_vtx(s, &s->combs[i], s->combs[i].startex, 0, fixup) != 0) { + if (s->verb > 1) + warning("Unable to locate vertex at node comb %s\n",pcomb(di,s->combs[i].nix)); + s->posfails++; + s->posfailstp++; + if (abortonfail) + break; + + } else { + s->combs[i].pvalid = 1; + } + } + } /* Next replacement vertex */ + + /* If we aborted because abortonfail is set and we failed to place a new node, */ + /* erase our tracks and return failure. */ + if (i < nncombs) { + for (i = 0; i < nncombs; i++) { + if (s->combs[i].vv != NULL) { + s->combs[i].vv->add = 0; + s->combs[i].vv->del = 0; + } + } + return 0; + } + +#ifdef DEBUG + printf("\nNow converting positioned combinations to vertexes\n"); +#endif + /* Convert from computed position to vertexes */ + for (i = 0; i < nncombs; i++) { + +#ifdef INDEP_SURFACE + /* Ignore combo that doesn't form a visible new vertex */ + if (sm_test(s, &s->combs[i].vm) == 0) + continue; +#endif /* INDEP_SURFACE */ + + if (s->combs[i].vv == NULL) { /* Not an existing vertex */ + + if (s->combs[i].pvalid == 0) { /* No valid vertex found */ + + /* [ If a valid vertex location was not found we tried */ + /* using the deleted vertex's location instead, and */ + /* relying on it getting deleted at some later stage. */ + /* This seems to stuff the incremental fixup code up */ + /* completely, so we create a summy vertex position instead. ] */ + + /* Fake up a vertex position when position_vtx() has failed. */ + dummy_vtx_position(s, ev1, ev2, &s->combs[i]); + goto lnew_vtx; + + } else { + + lnew_vtx:; + /* Allocate space for new vertex, and create it from location */ + s->combs[i].vv = new_vtx(s); + +#ifdef DEBUG + printf("Converting comb %s to from del %d !del %d to vtx no %d vm %s\n",pcomb(di,s->combs[i].nix),s->combs[i].v1[0]->no, s->combs[i].v2[0]->no, s->combs[i].vv->no,psm(s,&s->combs[i].vm)); +#endif + + for (e = 0; e < di; e++) { + s->combs[i].vv->nix[e] = s->combs[i].nix[e]; + s->combs[i].vv->ce[e] = s->combs[i].ce[e]; + s->combs[i].vv->p[e] = s->combs[i].p[e]; + s->combs[i].vv->v[e] = s->combs[i].v[e]; + } + s->combs[i].vv->nix[e] = s->combs[i].nix[e]; + s->combs[i].vv->nix[MXPD+1] = s->combs[i].nix[MXPD+1]; /* Copy Hash */ + s->combs[i].vv->nix[MXPD+2] = s->combs[i].nix[MXPD+2]; /* Copy nixm */ + + s->combs[i].vv->eperr = s->combs[i].eperr; + s->combs[i].vv->eserr = s->combs[i].eserr; + + /* Count the number of gamut surfaces the vertex falls on */ + det_vtx_gsurf(s, s->combs[i].vv); + + /* Check if the node and vertex cooincide, and aren't going to move */ + if (nn->nsp == di && s->combs[i].vv->nsp == di + && nn->pmask == s->combs[i].vv->pmask) { +//printf("~1 Trapped node and vertex coincide - mark vertex as ghost\n"); + s->combs[i].vv->ghost = 1; + } + s->combs[i].vv->del = 0; + s->combs[i].vv->add = 1; /* New vertex */ + } + } + + if (s->combs[i].vv != NULL) { /* There is a new or updated vertex */ +#ifdef DEBUG + printf("Vertex no %d pmask 0x%x cmask 0x%x vm %s at %s being added to batch list\n",s->combs[i].vv->no, s->combs[i].vv->pmask, s->combs[i].vv->cmask, psm(s,&s->combs[i].vm),ppos(di,s->combs[i].vv->p)); +#endif + /* Add to batch update list if it is not already there */ + if (s->combs[i].vv->bch == 0) { +//printf("~1 adding vtx 0x%x no %d to batch list\n",s->combs[i].vv,s->combs[i].vv->no); + s->combs[i].vv->batch = s->batch; + s->batch = s->combs[i].vv; + s->combs[i].vv->bch = 1; + } + +#ifdef INDEP_SURFACE + /* Set or update the vm */ + sm_or(s, &s->combs[i].vv->buvm, &s->combs[i].vv->buvm, &s->combs[i].vm); +//printf("~1 Vertex no %d buvm set/update to %s\n",s->combs[i].vv->no,psm(s,&s->combs[i].vv->buvm)); +#endif /* INDEP_SURFACE */ + } + } + + /* Add all vtx marked for deletion to the batch update list. */ + for (ev1 = s->nxh; ev1 != NULL; ev1 = ev1->nxh) { + +#ifdef DEBUG + printf("Vertex no %d being added to pending delete batch list, bdvm %s\n",ev1->no,psm(s,&ev1->bdvm)); +#endif + + if (ev1->bch == 0) { +//printf("~1 adding vtx 0x%x no %d to batch list\n",ev1,ev1->no); + ev1->batch = s->batch; + s->batch = ev1; + ev1->bch = 1; + } + +#ifdef INDEP_SURFACE + /* Add node setmask to those that will be removed delete vertex visibility */ +# ifdef USE_DISJOINT_SETMASKS + sm_orand(s, &ev1->bdvm, &ev1->bdvm, &s->sc[nn->pmask].a_sm, &s->sc[ev1->cmask & nn->pmask].a_sm); +# else + sm_or(s, &ev1->bdvm, &ev1->bdvm, &s->sc[nn->pmask].a_sm); +# endif +#endif /* INDEP_SURFACE */ + } + + /* Do first part of batch update. */ + /* This will reset ->del on nodes that will be retained */ + do_batch_update1(s, fixup); + + /* Remove deleted vertex's from the vertex net, and their */ + /* parent nodes. */ + { + vtx *vx1, *vx2; + for (vx2 = s->nxh; vx2 != NULL; vx2 = vx2->nxh) { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + +//printf("~1 Removing deleted vertex no %d from net\n",vx2->no); + if (vx2->del == 0) { /* It's not really being deleted */ +//printf("~1 vtx no %d is being retained\n",vx2->no); + continue; + } + + /* Remove from vertex net */ + for (j = 0; j < vx2->nnv; j++) { + vx1 = vx2->nv[j]; + +//printf("~1 Removing vtx no %d from vtx no %d\n",vx2->no, vx1->no); +// if (vx1->del == 0) { } /* Speed optimization */ + { + vtx_rem_vertex(s, vx1, vx2); + } +//else printf("~1 Not removing from vtx no %d because it will be deleted anyway\n",vx1->no); + } + vx2->nnv = 0; + + /* Remove from parent nodes */ + for (e = 0; e <= di; e++) { + int ix = vx2->nix[e]; + node *pp = s->n[ix]; + node_rem_vertex(s, pp, vx2); + } + } + } + + /* Create/modify the vertex neighbour net lists for all the new vertexes. */ + /* Use a brute force search of local nodes to create the vertex net. */ + { + vtx *vx1, *vx2; + + /* For each new vertx */ + for (i = 0; i < nncombs; i++) { + vx1 = s->combs[i].vv; + + if (vx1 == NULL || vx1->del) + continue; + + /* Possibly add other new vertexes as neighbours */ + for (j = i+1; j < nncombs; j++) { + + vx2 = s->combs[j].vv; + + if (vx2 != NULL && vx2->del == 0) + vtx_cnd_biadd_vtx(s, vx1, vx2, fixup); + } + + /* Possibly add deleted and non-deleted vertexes */ + for (k = 0; k < s->combs[i].count; k++) { + +#ifdef INDEP_SURFACE + /* Add deleted vertex if it isn't going to be deleted */ + if (s->combs[i].v1[k]->del == 0) { + vtx_cnd_biadd_vtx(s, vx1, s->combs[i].v1[k], fixup); + } +#endif /* INDEP_SURFACE */ + + /* Add non-deleted vertex */ + if (s->combs[i].v2[k]->del == 0) + vtx_cnd_biadd_vtx(s, vx1, s->combs[i].v2[k], fixup); + } + + /* Add any existing vertexes of the node we're re-adding */ + if (fixup) { + for (j = 0; j < nn->nvv; j++) { + if (nn->vv[j]->del) + continue; + vtx_cnd_biadd_vtx(s, vx1, nn->vv[j], fixup); + } + } + } + } + + /* Do second part of batch update */ + do_batch_update2(s, fixup); + +#ifdef DEBUG + printf("Add_to_vsurf done - added node %d\n",nn->ix); +#endif + + /* If we want intermediate fixup state: */ + /* dump_node_vtxs(s, 0); */ + /* sanity_check(s, 0); */ + +#ifdef NEVER +{ + vtx *vx; + + /* Dump vertex and associated vertex information */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + printf("Vertex no %d has Vtx net:",vx->no); + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + printf(" %d",vx2->no); + } + printf("\n"); + } + printf("\n"); + fflush(stdout); +} +#endif /* NEVER */ + + return 1; +} + +/* - - - - - - - - - - - */ +/* Deal with verticies marked for deletion or addition, */ +/* as well as updating the nodes consequently affects. */ +/* If fixup is set, add any new or updates vertexes to the s->fchl */ + +/* Do the first part of the batch update */ +static void do_batch_update1(ofps *s, int fixup) { + int e, di = s->di; + vtx *vv, *nvv; + node *pp; + +#ifdef DEBUG + printf("Doing batch update to add/delete vertexes - 1\n"); + +#endif + /* Update a vertexes vm, and decide whether it is going */ + /* to be deleted or just hidden. */ + for (vv = s->batch; vv != NULL; vv = vv->batch) { + +#ifdef DEBUG + printf("Pending vtx no %d del %d, add %d, vm %s |= %s &= %s\n",vv->no,vv->del,vv->add,psm(s,&vv->vm),psm(s,&vv->buvm),psm(s,&vv->bdvm)); + if (vv->ofake) + error("An ofake vertex no %d was hit!\n",vv->ofake); +#endif + + if (vv->add == 1) { /* New node */ + +#ifdef INDEP_SURFACE + sm_or(s, &vv->vm, &vv->vm, &vv->buvm); +#ifdef DEBUG + printf("Set vertex no %d vm to %s\n",vv->no,psm(s,&vv->vm)); +#endif +#endif /* INDEP_SURFACE */ + + /* Add it to the cache */ + vtx_cache_add(s, vv); + + /* Add it to the spatial accelleration grid */ + ofps_add_vacc(s, vv); + + /* Add to seeding lists */ + ofps_add_vseed(s, vv); + + } else if (vv->add == 2) { /* Update the visibility setmask */ + int was_inseed = 0, is_inseed = 0; + +#ifdef INDEP_SURFACE + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + was_inseed = 1; + + sm_or(s, &vv->vm, &vv->vm, &vv->buvm); +#ifdef DEBUG + printf("Updated vertex no %d vm to %s\n",vv->no,psm(s,&vv->vm)); +#endif + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + is_inseed = 1; + + if (vv->used == 0) { + /* Adjust presense in eserr tree if visibility has changed */ + if (was_inseed && !is_inseed) { +//printf("Removing (1) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + if ((aat_aerase(s->vtrees[vv->nsp], (void *)vv)) == 0) + error("aat_aerase vertex failed to find vertex no %d (1)", vv->no); + } else if (!was_inseed && is_inseed) { +//printf("Adding (1) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + if ((aat_ainsert(s->vtrees[vv->nsp], (void *)vv)) == 0) + error("aat_ainsert vertex malloc failed"); + } + } +#endif /* INDEP_SURFACE */ + + } else if (vv->del != 0) { + +#ifdef INDEP_SURFACE + int was_inseed = 0, is_inseed = 0; + + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + was_inseed = 1; +//printf("Checked was_inseed %d for vtx no %d, used %d, eserr %f, vm %s nsp %d\n",was_inseed,vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + + /* Remove visibility due to any deletes */ + sm_andnot(s, &vv->vm, &vv->vm, &vv->bdvm); + + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + is_inseed = 1; +//printf("Checking is_inseed %d for vtx no %d, used %d, eserr %f, vm %s nsp %d\n",is_inseed,vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + + /* Adjust presense in eserr tree if visibility has changed */ + if (vv->used == 0 && was_inseed && !is_inseed) { +//printf("Removing (2) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + if ((aat_aerase(s->vtrees[vv->nsp], (void *)vv)) == 0) + error("aat_aerase vertex failed to find vertex no %d (2)", vv->no); + } +#ifdef DEBUG + printf("Delete vertex no %d vm to %s\n",vv->no,psm(s,&vv->vm)); +#endif + /* Don't delete vertex if it remains visible to some sub-surfaces. */ + if (sm_test(s, &vv->vm) != 0) { + vv->del = 0; + vv->add = 2; /* Update it instead */ +#ifdef DEBUG + printf("Retaining vtx no %d marked for deletion because vm is %s\n",vv->no,psm(s,&vv->vm)); +#endif + } +#endif /* INDEP_SURFACE */ + } + } +} + +/* Do the second part of the batch update */ +static void do_batch_update2(ofps *s, int fixup) { + int e, di = s->di; + vtx *vv, *nvv; + node *pp; + +#ifdef DEBUG + printf("Doing batch update to add/delete vertexes - 2\n"); + +#endif + /* Add or delete a vertex */ + for (vv = s->batch; vv != NULL; vv = nvv) { + + nvv = vv->batch; + vv->batch = NULL; /* Ready for next time */ + vv->bch = 0; + + /* Setup vertex ready for another round */ + sm_set(s, &vv->buvm, 0); /* Ready to OR in new visibility next time */ + sm_set(s, &vv->bdvm, 0); /* Ready for OR in visibility to be remove next time */ + + if (vv->del) { /* delete vertex */ + +#ifdef DEBUG + printf("Deleting vertex no %d\n",vv->no); fflush(stdout); +#endif + /* Add all the parent nodes of this vertex to the update list */ + for (e = 0; e <= di; e++) { + int ix = vv->nix[e]; + node *pp = s->n[ix]; + + if (pp->upflag != s->flag) { + pp->nup = s->nup; + s->nup = pp; + pp->upflag = s->flag; + } + /* During fixups, maintain nodes vertexes lists */ + /* (During re-seeding we update it as a batch) */ + if (fixup) + node_rem_vertex(s, pp, vv); + } + del_vtx(s, vv); + +#ifdef DEBUG + { + vtx *vx; + int i, k; + + printf("~1 checking that no references to vertex remain after delete:\n"); + /* Check vertexes references to vertexes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + for (k = 0; k < vx->nnv; k++) { + if (vx->nv[k] == vv) { + printf("Vertex 0x%x no %d still in no %d nv list after deletion\n",vv,vv->no,vx->no); +#ifdef WARNINGS + warning("Vertex 0x%x no %d still in no %d nv list after deletion",vv,vv->no,vx->no); +#endif + } + } + } + /* Check nodes references to vertexes */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + for (k = 0; k < p1->nvv; k++) { /* For all its vertexes */ + if (p1->vv[k] == vv) { + printf("Vertex 0x%x no %d still in ix %d vv list after deletion\n",vv,vv->no,p1->ix); +#ifdef WARNINGS + warning("Vertex 0x%x no %d still in ix %d vv list after deletion",vv,vv->no,p1->ix); +#endif + } + } + } + /* Fixup sorted list reference to vertex */ + for (i = 0; i < s->nsvtxs; i++) { + if (s->svtxs[i] == vv) { + printf("Vertex 0x%x no %d still in svtxs[%d] after deletion\n",vv,vv->no,i); +#ifdef WARNINGS + warning("Vertex 0x%x no %d still in svtxs[%d] after deletion",vv,vv->no,i); +#endif + } + } + } +#endif /* DEBUG */ + + } else if (vv->add != 0) { /* New or updated vertex, set updates & checks */ + +#ifdef DEBUG + printf("Adding vertex no %d\n",vv->no); +#endif + + /* Add all the parent nodes of this vertex to the update list */ + for (e = 0; e <= di; e++) { + node *pp = s->n[vv->nix[e]]; + + if (pp->upflag != s->flag) { + /* Add node to update list */ + pp->nup = s->nup; + s->nup = pp; + pp->upflag = s->flag; + } + + /* During fixups, maintain nodes vertexes lists. */ + /* (During re-seeding we update it as a batch) */ + if (fixup && vv->add == 1) + node_add_vertex(s, pp, vv); + } + + /* If this is a fixup and the vertex hasn't been added */ + /* to the "check" list, do so */ + if (fixup && vv->fflag != s->fflag) { + vv->fchl = s->fchl; /* Add vertex to the "to be checked" list */ + if (s->fchl != NULL) + s->fchl->pfchl = &vv->fchl; + s->fchl = vv; + vv->pfchl = &s->fchl; + vv->fflag = s->fflag; +#ifdef DEBUG + printf("Adding vtx no %d to check list due to addition\n",vv->no); +#endif + } + } + } + + s->batch = NULL; /* Nothing in pending delete list */ + s->nup = NULL; /* Nothing in nodes to be updated list */ +} + +/* ------------------------------------------------------------------------------- */ + +/* Do a quick count of the number of verticies hit by their */ +/* neighbour nodes. This is used during itteration to decide */ +/* whether to reseed or fixup. */ +/* Return the number of vertexes hit */ +static int +ofps_quick_check_hits(ofps *s) { + int i, j, k, e, di = s->di; + int nvxhits = 0; + + /* For all nodes */ + for (i = -s->nbp; i < s->np; i++) { + node *nn = s->n[i]; /* Node being considered */ + + s->flag++; /* Marker flag for testing this node */ + nn->flag = s->flag; + + /* Check all the neighbors nodes */ + for (j = 0; j < nn->nvn; j++) { + node *pp = s->n[nn->vn[j]]; + + /* Test nn against all of pp's vertexes */ + for (k = 0; k < pp->nvv; k++) { + vtx *vx = pp->vv[k]; + + if (vx->cflag == s->flag) + continue; /* Don't test same node twice */ + vx->cflag = s->flag; + + /* If node that we're testing against is in vertex */ + /* ignore it, we expect them to hit. */ + for (e = 0; e <= di; e++) { + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { + continue; + } + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + if (nn->ix < 0) { + pleq *vp = &s->gpeqs[-1 - nn->ix]; + double v = 0.0; + + /* See if the vertex is on the wrong side of the plane */ + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + + if (v > 0.0) { + nvxhits++; + } + } else { + double eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); + + /* See if the vertex eperr will be improved */ + if (eperr < (vx->eperr + 0.0)) { + nvxhits++; + } + } + } + } + } + return nvxhits; +} + +/* ------------------------------------------------------------------------------- */ + +/* Recursive hit search routine: */ + +/* Test a vertex for a possible hit from a node. */ +/* Recursively search dorec recursions until there is at least */ +/* one hit, and stop after beyhit recursions beyond the hit region. */ +/* s->flag is assumed to be relevant for the given node. */ +/* Any hit vertexes are added to the s->nxh list. */ +/* s->vvchecks and s->nvcheckhits will be updated. */ +/* Return nz if vertex was hit */ + +/* Breadth first search. */ +/* (Assumes s->flag has been incremented) */ +/* [We're assuming that there is a single connected hit region, which */ +/* is not valid for SUBD. ] */ +static int +ofps_check_vtx(ofps *s, node *nn, vtx *vx, int dorec, int beyhit) { + int i, j, e, di = s->di; + vtx *slist = NULL; /* Next to search list */ + int dist; /* Distance from initial vertex */ + int hit = 0; + double tol = 0.0; /* Tollerance */ + +#ifdef DEBUG + printf("ofps_check_vtx() for node ix %d starting at vertex no %d, dorec %d, beyhit %d\n",nn->ix,vx->no,dorec,beyhit); +#endif + +#ifdef SANITY_CHECK_HIT + if (nn->ix < -s->nbp) + error("Calling ofps_check_node on fake outside node"); +#endif + + if (vx->cflag == s->flag) + return vx->del; /* Already been checked */ + + /* Put the starting node on the search list */ + vx->slist = slist; + slist = vx; + vx->sflag = s->flag; /* Mark as done for pre-hit search */ + + /* until we run out of vertexes, or we are done */ + for (dist = 0; slist != NULL && dist <= dorec; dist++) { + vtx *nvx; + + /* For each vertex in the search list, check it and recursion. */ + for (vx = slist, slist = NULL; vx != NULL; vx = nvx) { + nvx = vx->slist; + vx->opqsq = 0; /* Not on the list anymore */ + + if (vx->ofake) + continue; /* ofake vertexes can't be hit */ +#ifdef DEBUG + printf("%d: Checking vtx no %d %s\n",dist, vx->no,hit ? "Post-Hit" : "Pre-Hit"); +#endif +#ifdef INDEP_SURFACE + /* Only check for hit if the vertex is visible to the node */ + if (sm_vtx_node(s, vx, nn) == 0) { +# ifdef DEBUG + printf("%d: Vertex no %d xmask 0x%x vm %s isn't visible to ix %d pmask 0x%x a_sm %s\n",dist,vx->no,vx->cmask,psm(s,&vx->vm),nn->ix,nn->pmask,psm(s,&s->sc[nn->pmask].a_sm)); +# endif /* DEBUG */ + continue; + } +#endif /* INDEP_SURFACE */ + + /* If the vertex hasn't been checked yet: */ + if (vx->cflag != s->flag) { + vx->add = 0; + vx->del = 0; + vx->par = 0; + + s->vvchecks++; /* Checking a vertex */ + + /* Check if node is already parent to this vertex. */ + /* This only happens during fixups if the reposition fails and we */ + /* retain the vertex with the deleted vertex location (not currently */ + /* done), or by slim numerical margine, so ignore such hits. */ + /* We treat a parent as a hit node for the purposes of recursion, */ + /* and add it to a special list used to complete the vertex net. */ + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { +#ifdef DEBUG + printf("Vertex no %d has already got node ix %d\n",vx->no,nn->ix); +#endif + vx->par = 1; + } + } + + if (nn->ix < 0) { + pleq *vp = &s->gpeqs[-1 - nn->ix]; + double v = 0.0; + + /* See if the vertex is on the wrong side of the plane */ + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + + if (!vx->par && v > tol) { + s->nvcheckhits++; + if (!hit) + slist = nvx = NULL; /* Abort pre-hit search */ + hit = 1; + vx->del = 1; /* Mark for deletion */ + vx->nxh = s->nxh; /* Add vertex to list */ + s->nxh = vx; + vx->disth = 0; /* This is a hit vertex */ + vx->hflag = s->flag; +#ifdef DEBUG + printf("%d: Gamut surface boundary plain hit by %f\n",dist,v); +#endif + } +#ifdef DEBUG + else { /* If worse */ + printf("%d: Gamut surface boundary plain miss by %f\n",dist,v); + } +#endif + } else { /* Node rather than boundary plane */ + + /* nba_eperr is assumed to be valid if vx->cflag == s->flag */ + vx->nba_eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); +#ifdef DEBUG + printf("%d: Computing nba_eperr of %f for vtx no %d\n",dist, vx->nba_eperr, vx->no); +#endif + /* See if the vertex eperr will be improved */ + if (!vx->par && (vx->eperr - vx->nba_eperr) > tol) { + s->nvcheckhits++; + if (!hit) + slist = nvx = NULL; /* Abort pre-hit search */ + hit = 1; + vx->del = 1; /* Mark for deletion */ + vx->nxh = s->nxh; /* Add vertex to list */ + s->nxh = vx; + vx->disth = 0; /* This is a hit vertex */ + vx->hflag = s->flag; +#ifdef DEBUG + printf("%d: Vertex error improvement hit by %f (%f < %f)\n",dist, vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + + if (vx->par) { + printf("Vertex no %d hit by its own parent ix %d\n",vx->no, nn->ix); +#ifdef WARNINGS + warning("Vertex no %d hit by its own parent ix %d",vx->no, nn->ix); +#endif + } +#endif + } +#ifdef DEBUG + else { /* If worse */ + printf("%d: Vertex error not hit by %f (%f < %f)\n",dist, vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + } +#endif + } + vx->cflag = s->flag; +// ~~777 +//if (vx->del && i_rand(1,1000) == 15) { +// printf("~1 failing to check vertex no %d\n",vx->no); +// vx->cflag = s->flag -1; +// vx->del = 0; +//} + } + + /* Decide whether to recurse by adding vertexes to the new list */ + if (!hit) { + + /* Pre-hit recursion */ + if (dist < dorec) { /* Still within search radius */ + + /* Add all the unsearched vertexes neighbors to the next search list */ + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + + if (vx2->sflag == s->flag) /* Already been pre-hit searched */ + continue; +#ifdef DEBUG + printf("%d: Adding vtx no %d to next pre-hit search list\n",dist, vx2->no); +#endif + /* Put the neighbour node on the search list */ + vx2->slist = slist; + slist = vx2; + vx2->sflag = s->flag; + } + } + + } else { + + /* Post hit recursion */ + if (vx->disth <= beyhit) { /* Still within post-hit search radius */ + int disth = vx->disth + 1; /* Neighbours distance */ + + /* Add all the unsearched vertexes neighbors to the next search list */ + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + + if (vx2->hflag == s->flag) { /* Already been post-hit searched */ + if (disth >= vx2->disth) + continue; /* So skip it */ + + /* The already post-hit searched neighbour has an improved distance */ +#ifdef DEBUG + printf("%d: Improving ph searched vtx %d disth from %d to %d\n",dist, vx2->no,vx2->disth,disth); +#endif + vx2->disth = disth; /* Improved distance to hit though */ + if (vx2->disth > beyhit || vx2->opqsq) + continue; /* But it's still too far, or already on the list */ + /* Search this neighbour again now that it is within radius */ + } else { + vx2->disth = disth; /* Set hit distance */ + } +#ifdef DEBUG + printf("%d: Adding vtx no %d to next post-hit search list (disth = %d)\n",dist, vx2->no,vx2->disth); +#endif + /* Put the neighbour node on the search list */ + vx2->slist = slist; + slist = vx2; + vx2->sflag = vx2->hflag = s->flag; + vx2->opqsq = 1; /* On the list */ + } + } +#ifdef DEBUG + else + printf("%d: Vertex %d disth %d is > beyhit %d so not recursing\n",dist, vx->no,vx->disth,beyhit); +#endif + + } + } /* Next vertex in current list */ +#ifdef DEBUG + printf("Finished inner loop because vx 0x%x = NULL\n",vx); +#endif + } /* Next list */ +#ifdef DEBUG + printf("Finished outer loop because slist 0x%x = NULL, || dist %d > dorec %d\n",slist,dist,dorec); +#endif + + return hit; +} + +/* Non-recursive version of above used for sanity checking */ +/* that doesn't set any flags on vx */ +static int +ofps_check_vtx_sanity(ofps *s, node *nn, vtx *vx, int fixit) { + int i, j, e, di = s->di; + vtx *slist = NULL; /* Next to search list */ + int dist; /* Distance from initial vertex */ + double tol = 1e-6; + int hit = 0; + int par = 0; + +#ifdef DEBUG + printf("ofps_check_vtx_sanity() for node ix %d and vertex no %d\n",nn->ix,vx->no); +#endif + if (vx->cflag == s->flag) { +#ifdef DEBUG + printf("Returning alread calculated del = %d\n",vx->del); +#endif + return vx->del; /* Already been checked */ + } + + if (vx->ofake) { /* ofake nodes can't be hit */ +#ifdef DEBUG + printf("Returning ofake del = 0\n"); +#endif + return 0; + } + +#ifdef INDEP_SURFACE + /* Only check for hit if the vertex is visible to the node */ + if (sm_vtx_node(s, vx, nn) == 0) { +#ifdef DEBUG + printf("Returning non-visible del = 0\n"); +#endif + return 0; + } +#endif /* INDEP_SURFACE */ + + /* Check if node is already parent to this vertex. */ + /* This only happens during fixups if the reposition fails and we */ + /* retain the vertex with the deleted vertex location (not currently */ + /* done), or by slim numerical margine, so ignore such hits. */ + /* We treat a parent as a hit node for the purposes of recursion, */ + /* and add it to a special list used to complete the vertex net. */ + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) + par = 1; + } + + if (nn->ix < 0) { + pleq *vp = &s->gpeqs[-1 - nn->ix]; + double v = 0.0; + + /* See if the vertex is on the wrong side of the plane */ + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + + if (!par && v > tol) { + hit = 1; + + if (fixit) { + vx->slist = slist; + slist = vx; + vx->sflag = s->flag; + } + } + } else { /* Node rather than boundary plane */ + double nba_eperr; + + nba_eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); + + /* See if the vertex eperr will be improved */ + if (!par && (vx->eperr - nba_eperr) > tol) { + hit = 1; + if (fixit) { + vx->slist = slist; + slist = vx; + vx->sflag = s->flag; + } + } + } + +#ifdef DEBUG + printf("Returning computed del = %d\n",hit); +#endif + return hit; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Add a node to the currnent voronoi. */ +/* Return nz if the addition fails due to there being no vetex hits or a cooincince. */ +/* Return nz if abortonfail is set and we fail to position the node. */ +/* (This theoretically shouldn't happen, but does, due to the perceptual */ +/* geometry ?) */ +static int add_node2voronoi( +ofps *s, +int poi, /* Index of sample point to update/create Voronoi surface */ +int abortonfail /* 0 = ignore position failures, 1 = abort add if there are any failures */ +) { + node *nn = s->n[poi]; /* Node in question */ + int e, di = s->di; + int i, j; + vtx *vx = NULL; /* Closest vertex */ + +#ifdef DEBUG + printf("\nAdding Node ix %d pmask 0x%x at %s (perc %s) to Voronoi surface\n",poi,nn->pmask,ppos(di,nn->p),ppos(di,nn->v)); +#endif + + if (poi < 0) + error("Attempt to add fake point to veronoi surface"); + + if (nn->nvv > 0) + error("ofps: assert, node vertex info should be empty on add_node2voronoi() entry"); + + for (i = 0; i < 20; i++) { + int pci; /* Point cell list index */ + acell *cp; /* Acceleration cell */ + node *pp; + + /* Check if by some misfortune, this node colides with an existing node. */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + for (pp = cp->head; pp != NULL; pp = pp->n) { + for (e = 0; e < di; e++) { + if (fabs(nn->v[e] - pp->v[e]) > (COINTOL * 100.0)) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Node oint collides with existing - joggling it\n"); +// warning("Node oint collides with existing - joggling it"); +#endif + /* Joggle it's position */ + for (e = 0; e < di; e++) { + if (nn->p[e] < 0.5) + nn->p[e] += d_rand(0.0, 1e-4); + else + nn->p[e] -= d_rand(0.0, 1e-4); + } + /* Ignore confine planes. Next itter should fix it anyway ? */ + ofps_clip_point6(s, nn->p, nn->p); + s->percept(s->od, nn->v, nn->p); + break; + } + } + if (pp == NULL) + break; + } + if (i >= 20) { + if (s->verb > 1) + warning("add_node2voronoi: Assert, was unable to joggle cooincindent point"); + return 1; + } + +#ifdef DEBUG + printf("Locating all the hit vertexs\n"); +#endif + + s->nvcheckhits = 0; /* Count number of vertexes hit by recursive check. */ + s->batch = NULL; /* Nothing in pending delete list */ + s->nup = NULL; /* Nothing in nodes to be updated list */ + s->flag++; /* Marker flag for adding this node */ + s->nxh = NULL; /* Nothing in nodes hit list */ + +#ifdef DEBUG + printf("Done check of vertexes for hits\n"); +#endif + + /* Number of nodes that would be checked by exaustive search */ + s->vvpchecks += s->nv; + + ofps_findhit_vtxs(s, nn); + +#ifdef SANITY_CHECK_HIT +#ifdef DEBUG + printf("Doing sanity check of hits\n"); +#endif + for (vx = s->uvtx; vx != NULL; vx = vx->link) { /* Check all vertexes */ + + if (vx->cflag != s->flag && ofps_check_vtx_sanity(s, nn, vx, 0)) { + warning("!!!!!! Sanity: Add hit missed vertex no %d at %s !!!!!!",vx->no,ppos(di,vx->p)); + printf("!!!!!! Sanity: Add hit missed vertex no %d at %s !!!!!!\n",vx->no,ppos(di,vx->p)); + /* Don't stop for out of gamut vertexes that would have been hit */ + if (ofps_would_clip_point(s, vx->p)) + continue; + + /* Check if any of it's neighbours have been checked. */ + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + + if (vx2->cflag == s->flag) + break; /* Yes */ + } + if (j >= vx->nnv) { + warning("!!!!!! Sanity: Missed vertex was in isolated region"); + } else { + warning("!!!!!! Sanity: Missed vertex was adjacent to no %d", vx->nv[j]->no); + } +#ifdef SANITY_CHECK_HIT_FATAL + error("Failed to locate all hit vertexes"); +#endif /* SANITY_CHECK_HIT_FATAL */ + } + } +#endif /* SANITY_CHECK_HIT */ + +#ifdef DEBUG + printf("There were %d vertexes that will be hit by adding node\n",s->nvcheckhits); +#endif + + if (s->nvcheckhits == 0) { + if (s->verb > 1) + warning("Failed to get any vertex hits when adding a new node ix %d at %s",nn->ix,ppos(di,nn->p)); + return 1; + } + + /* Now turn all the hit vertexes into new vertexes. */ + if (add_to_vsurf(s, nn, 0, abortonfail) > 0) { + s->add_hit++; + } else { + if (abortonfail) + return 1; + s->add_mis++; + } + + ofps_add_nacc(s, nn); /* Add to spatial accelleration grid */ + + s->np++; + +#ifdef DEBUG + printf("Done add_node2voronoi()\n"); +#endif + + return 0; +} + +/* ------------------------------------------------------------------------------- */ + +/* Given a list of di plane equations, */ +/* compute the intersection point. */ +/* return nz if there is no intersection */ +static int comp_vtx(ofps *s, double *p, pleq **peqs) { + int i, e, di = s->di; + double **ta, *TTA[MXPD], TA[MXPD][MXPD]; + + for (e = 0; e < di; e++) + TTA[e] = TA[e]; + ta = TTA; + + for (i = 0; i < di; i++) { + for (e = 0; e < di; e++) + ta[i][e] = peqs[i]->pe[e]; /* Plane normal becomes row of matrix */ + p[i] = -peqs[i]->pe[di]; /* Plane constant becomes target */ + } + /* Solve the simultaneous linear equations A.x = B */ + /* Return 1 if the matrix is singular, 0 if OK */ + if (polished_solve_se(ta, p, di)) + return 1; + + return 0; +} + +/* --------------------------------------------------- */ + +/* Use a brute force search to (re-)create the vertex net. */ +/* This is used in initialization. */ +static void create_vtx_net(ofps *s) { + int ff, f, e, di = s->di; + vtx *vx1, *vx2; + +#ifdef DEBUG + printf("Doing create_vtx_net\n"); +#endif + + /* For each vertx */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + + vx1->nnv = 0; /* Clear the current list */ +#ifdef DEBUG + printf("Creating neighbourhood net for vtx no %d\n",vx1->no); +#endif + + /* Search all other vertexes for neighbours */ + for (vx2 = s->uvtx; vx2 != NULL; vx2 = vx2->link) { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + +//printf("~1 checking against vtx %d\n",vx2->no); + if (vx1 == vx2) { +//printf("~1 skip because it's the same\n"); + continue; + } + + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = vx1->nix[MXPD+2]; /* nixm */ + bb = vx2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) { +//printf("~1 skip because nixm 0x%x and 0x%x don't match\n",aa,bb); + continue; /* It's certainly not */ + } + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (vx1->nix[e] == vx2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (vx1->nix[e] > vx2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) { +//printf("~1 skip because nix %s and %s aren't one different\n",pcomb(di,vx1->nix),pcomb(di,vx2->nix)); + continue; /* No match */ + } + + if (nnm == 0) { + error("ofps: two vertexes have the same nodes !\n" + "no %d at %s nix %s\nno %d at %s nix %s", + vx1->no,ppos(di,vx1->p),pcomb(di,vx1->nix), + vx2->no,ppos(di,vx2->p),pcomb(di,vx2->nix)); + } + + /* vx2 is a neighbour, so add it to the vtx net */ + vtx_add_vertex(s, vx1, vx2); +//printf("~1 brute force: adding vtx %d as neighbour to %d\n",vx2->no,vx1->no); + } + } +} + +/* --------------------------------------------------- */ + +/* Use a brute force search to discover all the valid */ +/* sub-surface combinations. */ +static void discover_subsuf(ofps *s) { + int co; + double p[MXPD]; + pleq *peqs[MXPD]; + int i, j, k, e, di = s->di; + setmask acm; /* Accumulated mask for special last entry */ + + if (s->sminit) + return; /* Do this once */ + +#ifdef DEBUG + printf("Computing subd face combinations\n"); +#endif + + if ((s->sc = (surfcomb *)calloc(sizeof(surfcomb), (1 << s->nbp))) == NULL) + error ("ofps: malloc failed on sufcomb array"); + + for (co = 0; co < (1 << s->nbp); co++) { + + s->sc[co].co = co; + + /* Count number of planes */ + for (i = e = 0; e < s->nbp; e++) { + if (co & (1 << e)) + i++; + /* Skip combo if odd and even dimension planes are set */ + if ((e & 1) == 0 && e < (2 * di) && (co & (1 << e)) && (co & (1 << (e+1)))) + break; + } + s->sc[co].nos = i; + if (i > di || e < s->nbp) { + s->sc[co].valid = 0; + continue; + } + + /* Check that the combination results in a valid */ + if (i == di) { + for ( j = e = 0; e < s->nbp; e++) { + if (co & (1 << e)) + peqs[j++] = &s->gpeqs[e]; + } + if (comp_vtx(s, p, peqs) != 0 || ofps_would_clip_point(s, p)) { + s->sc[co].valid = 0; + } else { + s->sc[co].valid = 1; + } + } else { + if (co == 0 || i == 1) { + s->sc[co].valid = 1; + } else { + s->sc[co].valid = -1; + } + } +//printf("~1 val %s sc[%d].valid = %d\n",icmPdv(di, p), co,s->sc[co].valid); + } + /* Go through the unknown combinations, and see if there */ + /* is a valid lower dimensional combination that is valid. */ + for (co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == -1) { + for (i = co+1; i < (1 << s->nbp); i++) { + if ((i & co) == co && s->sc[i].valid == 1) { + s->sc[co].valid = 1; + break; + } + } + if (i >= (1 << s->nbp)) /* Failed to find a valid combination */ + s->sc[co].valid = 0; + } + } +#ifdef USE_DISJOINT_SETMASKS + /* We can reduce the number of setmask bits by figuring out which */ + /* combinations are disjoint, and using the same setmask bits for disjoint */ + /* combinations. For CMYK, this reduces the setmask from 80-100 to less than 32 bits, */ + /* permiting faster mask manipulation. */ + { + surfcomb *scp, *zd = NULL; /* Zero Dimension combinations */ + surfcomb *sets = NULL; /* Sets at a given nos */ + int nsets = 0; /* Current number of sets */ + int _nsets = 0; /* Allocated array size */ + int nos; /* Number of surfaces */ + + /* init the circular lists, and add the 0D points to their list */ + for (k = co = 0; co < (1 << s->nbp); co++) { + s->sc[co].ds = &s->sc[co]; /* Init circular list to itself */ + if (s->sc[co].valid == 0) + continue; + /* Create a list of 0D points and count them */ + if (s->sc[co].nos == di) { + k++; + if (zd == NULL) + zd = &s->sc[co]; + else { + s->sc[co].ds = zd->ds; + zd->ds = &s->sc[co]; + } + } + } + + if (zd == NULL) + error("No zero-dim surface combinations (s->nbp = %d)",s->nbp); + +//printf("~1 total 0D points = %d\n",k); + + /* Temporarily use the setmask to track 0D hits */ + sm_init(s, k); + + k = 2; /* Count total disjoint sets, including 2 for di D and 0 D */ + + /* Locates sets for each dimension level */ + for (nos = 1; nos < di; nos++) { + nsets = 0; + +//printf("~1 doing nos = %d\n",nos); + /* Add the next combination to the sets */ + for (co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0 || s->sc[co].nos != nos) + continue; + +//printf("~1 checking combo 0x%x\n",co); + /* Figure out 0D hits on this combo */ + i = 0; + scp = zd; + do { + if ((co & scp->co) == co) + sm_setbit(s, &s->sc[co].i_sm, i, 1); + i++; + scp = scp->ds; + } while(scp != zd); +//printf("~1 combo 0x%x has hits %s\n",co,psm(s,&s->sc[co].i_sm)); + + /* Search through the existing sets, and see */ + /* if this combo is disjoint */ + for (j = 0; j < nsets; j++) { + setmask tsm; + + if (sm_and(s, &tsm, &sets[j].i_sm, &s->sc[co].i_sm) == 0) { + /* Add this combo to the existing set */ + +//printf("~1 adding to set %d\n",j); + s->sc[co].ds = sets[j].ds->ds; + sets[j].ds->ds = &s->sc[co]; + sm_or(s, &sets[j].i_sm, &sets[j].i_sm, &s->sc[co].i_sm); + break; + } +//else printf("Miss on set %d hits %s, AND %s\n",j,psm(s,&sets[j].i_sm),psm(s,&tsm)); + } + /* If we can't use an existing set, create a new one */ + if (j >= nsets) { + if (nsets >= _nsets) { + _nsets = 2 * _nsets + 5; + if ((sets = (surfcomb *)realloc(sets, sizeof(surfcomb) * _nsets)) == NULL) + error("malloc failed on disjoint sets size %d", _nsets); + } + sm_cp(s, &sets[j].i_sm, &s->sc[co].i_sm); /* Hits to this set */ + sets[j].ds = &s->sc[co]; /* Only entry in circular list */ +//printf("New set %d hits %s\n",j,psm(s,&sets[j].i_sm)); + nsets++; + k++; + } + } + } + + if (sets != NULL) + free(sets); + +#ifdef DEBUG + printf("Total number of setmask disjoint sets = %d\n",k); +#endif + + /* Setup the setmask params */ + sm_init(s, k); + + /* Assign the individual setmask bits */ + for (i = co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0 || s->sc[co].smset == 1) + continue; + +//printf("~1 setting mask bit on comb 0x%x and its set\n",co); + /* Assign setmask to all in this set */ + scp = &s->sc[co]; + do { +//printf("~1 setting mask bit %d on comb 0x%x\n",i,scp->co); + sm_set(s, &scp->i_sm, 0); /* Clear temporary hit mask */ + sm_setbit(s, &scp->i_sm, i, 1); + scp->smset = 1; + scp = scp->ds; + } while(scp != &s->sc[co]); + i++; + } + + } +#else /* !USE_DISJOINT_SETMASKS */ + + /* Count the number of valid combinations */ + for (i = co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0) + continue; + i++; + } +#ifdef DEBUG + printf("Total number of setmask sets = %d\n",i); +#endif + + /* Setup the setmask params */ + sm_init(s, i); + + /* Assign the individual setmask bits */ + for (i = co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0) + continue; + sm_setbit(s, &s->sc[co].i_sm, i, 1); + i++; + } +#endif /* !USE_DISJOINT_SETMASKS */ + + sm_set(s, &acm, 0); /* Init overall accumulated mask */ + + /* Compute the accumulated setmask bits */ + for (i = 0; i < (1 << s->nbp); i++) { + if (s->sc[i].valid == 0) + continue; + for (j = 0; j < (1 << s->nbp); j++) { + if ((i & j) != j || s->sc[j].valid == 0) + continue; + sm_or(s, &s->sc[i].a_sm, &s->sc[i].a_sm, &s->sc[j].i_sm); + } + sm_or(s, &acm, &acm, &s->sc[i].i_sm); + } + + /* Set special "all planes, all valid" combination as the last */ + /* entry for use by fake surface nodes. */ + s->sc[(1 << s->nbp)-1].valid = 1; + s->sc[(1 << s->nbp)-1].nos = s->nbp; + sm_cp(s, &s->sc[(1 << s->nbp)-1].i_sm, &acm); + sm_cp(s, &s->sc[(1 << s->nbp)-1].a_sm, &acm); + s->sc[(1 << s->nbp)-1].smset = 1; + s->sc[(1 << s->nbp)-1].ds = NULL; + +#ifdef MAXINDEP_2D + /* Go through the combinations and invalidate any */ + /* that are not full-d or more than 2D */ + for (co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid) { +// if (s->sc[co].nos != 0 && (di - s->sc[co].nos) > 1) // test in 3D + if (s->sc[co].nos != 0 && (di - s->sc[co].nos) > 2) + s->sc[co].valid = 0; + } + } +#endif /* MAXINDEP_2D */ + +#ifdef DEBUG + /* Print diagnostics */ + for (i = 0; i < (1 << s->nbp); i++) { + if (s->sc[i].valid == 0) + continue; + printf(" Mask 0x%x, setmasks i = %s, a = %s\n",i,psm(s,&s->sc[i].i_sm),psm(s,&s->sc[i].a_sm)); + } +#endif + + s->sminit = 1; +} +/* --------------------------------------------------- */ + +/* Compute a simple but unbounded model of the */ +/* perceptual function. We use the current vertex values */ +/* to setup the model */ +/* (It would be faster to do the optimization per output channel!) */ + +/* Matrix optimisation function handed to powell() */ +static double xfitfunc(void *edata, double *x) { + ofps *s = (ofps *)edata; + int e, di = s->di; + double rv = 0.0; + vtx *vx; + + /* For all the vertexes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double v[MXPD], ev; + + /* Apply matrix cube interpolation */ + icxCubeInterp(x, di, di, v, vx->p); + + /* Evaluate the error */ + for (ev = 0.0, e = 0; e < di; e++) { + double tt; + tt = vx->v[e] - v[e]; + ev += tt * tt; + } + rv += ev; + } + +// printf("~1 rv = %f\n",rv); + + return rv; +} + +/* Fit the unbounded perceptual model to just the inside vertexes */ +static void init_pmod(ofps *s) { + int e, di = s->di; + double sa[MXPD * (1 << MXPD)]; + double rerr; + + /* Setup matrix to be closest values initially */ + for (e = 0; e < (1 << di); e++) { /* For each colorant combination */ + int j, f; + double bdif = 1e6; + double ov[MXPD]; + vtx *vx, *bvx = NULL; + + /* Search the vertex list to find the one closest to this input combination */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double dif = 0.0; + + if (vx->ofake) + continue; /* Ignore outside vertexes */ + + for (j = 0; j < di; j++) { + double tt; + if (e & (1 << j)) + tt = s->imax[j] - vx->p[j]; + else + tt = s->imin[j] - vx->p[j]; + dif += tt * tt; + } + if (dif < bdif) { /* best so far */ + bdif = dif; + bvx = vx; + if (dif < 0.001) + break; /* Don't bother looking further */ + } + } + for (f = 0; f < di; f++) + s->pmod[f * (1 << di) + e] = bvx->v[f]; + } + + for (e = 0; e < (di * (1 << di)); e++) + sa[e] = 10.0; + + if (powell(&rerr, di * (1 << di), s->pmod, sa, 0.001, 1000, + xfitfunc, (void *)s, NULL, NULL) != 0) { + if (s->verb > 1) + warning("Powell failed to converge, residual error = %f",rerr); + } + +#ifdef DEBUG + printf("Perceptual model fit residual = %f\n",sqrt(rerr)); +#endif + s->pmod_init = 1; +} + +/* --------------------------------------------------- */ +/* Init fake node contents, and setup the initial */ +/* voronoi surface with the first node. */ +static void ofps_binit(ofps *s) { + int e, di = s->di; + int doink = 0; + int i, j; + DCOUNT(co, MXPD, di, 0, 0, 2); /* Count through corner verticies */ + int iix = -2 * di - 2; /* Fake inside node index */ + node *inp = s->n[iix]; /* Fake inside node */ + int oix = -2 * di - 3; /* Fake outside node index */ + node *onp = s->n[oix]; /* Fake outside node */ + double ivtx_whts[] = { /* Initial vertex weightings */ + 3.7144267283692024e+165, + 1.3997102851752585e-152, + 6.1677886722367450e+223, + 1.7281009363426126e+097, + 2.0087766625640005e-139, + 4.9406564584124654e-323, + 7.7791723264315535e-260, + 8.5733372291341995e+170, + 6.0046007797559735e-067, + 2.8214561724952793e+243, + 5.0132438738338732e+262, + 1.6259745436952323e-260, + 7.9968034958246946e+001 + }; + double vtxwt; /* Combined initial vertexnode weighting */ + unsigned int fullmask = 0; + +#ifdef DEBUG + printf("Binit called\n"); +#endif + if (s->ilimit < (double)di) /* Ink limit is active */ + doink = 1; + + /* Init fake inside and outside node */ + inp->ix = iix; + inp->fx = 1; + inp->pmask = 0; + onp->ix = oix; + onp->fx = 1; + onp->pmask = 0; + + for (i = 0; i < (2 * di); i++) + fullmask |= 1 << i; + if (doink) + fullmask |= 1 << i; + + /* Init the axis aligned gamut surface plane equations */ + /* and also setup nodes that are indexes by the fake indexes */ + /* with just the information that will be used. */ + for (i = 0; i < (2 * di); i++) { /* unit cell at 0 */ + int ii = i >> 1; /* Dimension */ + int ix; /* Surface "node" index */ + pleq *vp; /* plane being initialized */ + node *np; + + ix = -i-1; /* -1 to -2di fake other nodes */ + vp = &s->gpeqs[-1-ix]; /* Pointer to plane associated with fake node */ + vp->ix = ix; + + for (e = 0; e < di; e++) + vp->pe[e] = 0.0; + vp->pe[ii] = i & 1 ? 1.0 : -1.0; /* Normal */ + vp->pe[di] = i & 1 ? -s->imax[ii] : s->imin[ii]; /* Constant */ + + np = s->n[ix]; + np->ix = ix; + np->fx = 1; /* They don't move */ + np->nsp = 1; + np->sp[0] = vp; + np->pmask = fullmask; +// np->pmask = 1 << i; /* fake surface node pmask is itself for cmask ?? */ +// onp->pmask = inp->pmask |= np->pmask; + } + s->nbp = 2 * di; /* Number of boundary planes */ + + /* Add ink limit surface plane and its fake node */ + if (doink) { /* Ink limit plane is orthogonal to diagonal */ + int ix; /* Surface "node" index */ + pleq *vp; /* plane being initialized */ + node *np; + double len; + + ix = -i-1; /* -1 to -2di fake other nodes */ + vp = &s->gpeqs[-1-ix]; /* Pointer to plane associated with fake node */ + vp->ix = ix; + len = 1.0/sqrt((double)di); /* Normalised length */ + for (e = 0; e < di; e++) + vp->pe[e] = len; + vp->pe[di] = -s->ilimit * len; + + np = s->n[ix]; + np->ix = ix; + np->fx = 1; /* They don't move */ + np->nsp = 1; + np->sp[0] = vp; + np->pmask = fullmask; +// np->pmask = 1 << i; /* fake surface node pmask is itself for cmask ?? */ +// onp->pmask = inp->pmask |= np->pmask; + s->nbp++; /* Number of boundary planes */ + } else { + s->n[-i-1]->ix = -i-1; /* Label unused node */ + } + +#ifdef DEBUG + printf("Number of boundary planes = %d\nDiscovering all valid veronoi sub-surfaces\n",s->nbp); +#endif + + discover_subsuf(s); + +#ifdef DEBUG + printf("Creating rectangular initial vertexes\n"); +#endif + + /* Compute initial node weighting */ + for (vtxwt = 0.0, i = 0; i < (sizeof(ivtx_whts)/sizeof(double)-1); i++) + vtxwt += log(ivtx_whts[i]); + vtxwt += ivtx_whts[i]; + + /* Create initial verticies, one for each di combination of planes, */ + /* and keep the ones that are in gamut. */ + DC_INIT(co); + while(!DC_DONE(co)) { + double p[MXPD]; + vtx *vi, *vo; /* Inside and outside vertex */ + + /* Compute vertex location */ + for (e = 0; e < di; e++) { + if (co[e] != 0) + p[e] = s->imax[e]; + else + p[e] = s->imin[e]; + } + + if (ofps_would_clip_point(s, p)) { +#ifdef DEBUG + printf("Position %s rejected, out of gamut\n",ppos(di,p)); +#endif + goto next_co; + } +#ifdef DEBUG + printf("Position %s accepted\n",ppos(di,p)); +#endif + + vi = new_vtx(s); + vo = new_vtx(s); + + for (e = 0; e < di; e++) + vi->p[e] = vtxwt * p[e]; + ofps_cc_percept(s, vi->v, vi->p); + + for (e = 0; e < di; e++) + vo->p[e] = (10.0 * (p[e] - 0.5)) + 0.5; +// ofps_cc_percept(s, vo->v, vo->p); + + /* Compute nodes involved */ + for (e = 0; e < di; e++) { + if (co[e] == 0) { + vo->nix[e] = vi->nix[e] = -1 - (2 * e + 0); + } else { + vo->nix[e] = vi->nix[e] = -1 - (2 * e + 1); + } + } + + vi->nix[di] = iix; /* First nodee */ +#ifdef DEBUG + printf("ivertex nix %s\n",pcomb(di,vi->nix)); +#endif + sort_nix(s, vi->nix); + vi->eperr = 10000.0; /* Very bad, so they get chosen first */ + vi->eserr = 10000.0; + + det_vtx_gsurf(s, vi); /* Set pmask & cmask */ + sm_cp(s, &vi->vm, &s->sc[vi->cmask].a_sm); /* Set visibility */ + + vi->ifake = 1; /* Inside fake */ + + vtx_cache_add(s, vi); /* Add it to the vertex cache and spatial accelleration grid */ + ofps_add_vacc(s, vi); + ofps_add_vseed(s, vi); + + vo->nix[di] = oix; /* Fake outside node */ +#ifdef DEBUG + printf("overtex nix %s\n",pcomb(di,vo->nix)); +#endif + sort_nix(s, vo->nix); + vo->eperr = vtxwt * -9.0; /* Better than zero error */ + vo->eserr = vtxwt * -9.0; + /* Leave pmask,cmask = 0 */ + vo->pmask = vi->pmask; /* Copy from inner vertexes */ + vo->cmask = vi->cmask; + sm_cp(s, &vo->vm, &s->sc[vo->cmask].a_sm); /* Set visibility */ + + vo->ofake = 1; /* Outside fake - don't plot vnets and don't use */ + /* for perceptual function extension. */ + vo->used = 1; /* Not a candidate for seeding */ + + next_co:; + DC_INC(co); + } + + /* Add ink limit vertexes */ + if (doink) { /* Ink limit plane is orthogonal to diagonal */ + COMBO(nco, MXPD, di-1, s->nbp-1); /* di-1 out of neighbor nodes combination counter */ + +#ifdef DEBUG + printf("Creating ink limit vertexes\n"); +#endif + /* Intersect the ink limit plane with each combination of */ + /* it and and di-1 of the existing planes, to generate */ + /* potential vertexes, and keep the ones that are in gamut. */ + CB_INIT(nco); + while (!CB_DONE(nco)) { + pleq *peqs[MXPD]; + double p[MXPD]; + + for (e = 0; e < (di-1); e++) { + peqs[e] = &s->gpeqs[nco[e]]; + } + peqs[e] = &s->gpeqs[2 * di]; + + /* Compute device location of intersection */ + if (comp_vtx(s, p, peqs) == 0) { + vtx *vi, *vo; /* Inside and outside vertex */ + + if (ofps_would_clip_point(s, p)) { +#ifdef DEBUG + printf("Position %s rejected, out of gamut\n",ppos(di,p)); +#endif + goto next_nco; + } +#ifdef DEBUG + printf("Position %s accepted\n",ppos(di,p)); +#endif + + vi = new_vtx(s); + vo = new_vtx(s); + + /* Device and perceptual */ + for (e = 0; e < di; e++) + vi->p[e] = vtxwt * p[e]; + ofps_cc_percept(s, vi->v, vi->p); + + for (e = 0; e < di; e++) + vo->p[e] = (10.0 * (p[e] - 0.5)) + 0.5; +// ofps_cc_percept(s, vo->v, vo->p); + + for (e = 0; e < (di-1); e++) + vo->nix[e] = vi->nix[e] = -1-nco[e]; /* Fake gamut surface plane nodes */ + vo->nix[e] = vi->nix[e] = -2 * di -1; /* Fake ink limit node */ + + vi->nix[di] = iix; /* First node */ +#ifdef DEBUG + printf("ivertex nix %s\n",pcomb(di,vi->nix)); +#endif + sort_nix(s, vi->nix); + vi->eperr = 10000.0; /* Very bad */ + vi->eserr = 10000.0; + + det_vtx_gsurf(s, vi); /* Set pmask & cmask */ + sm_cp(s, &vi->vm, &s->sc[vi->cmask].a_sm); /* Set visibility */ + + vi->ifake = 1; /* Inside fake */ + + vtx_cache_add(s, vi); /* Add to vertex cache and spatial accelleration grid */ + ofps_add_vacc(s, vi); + ofps_add_vseed(s, vi); + + vo->nix[di] = oix; /* Fake outside node */ +#ifdef DEBUG + printf("overtex nix %s\n",pcomb(di,vo->nix)); +#endif + sort_nix(s, vo->nix); + vo->eperr = vtxwt * -9.0; /* Better than zero error */ + vo->eserr = vtxwt * -9.0; + /* Leave pmask,cmask = 0 */ + vo->pmask = vi->pmask; /* Copy from inner vertexes */ + vo->cmask = vi->cmask; + sm_cp(s, &vo->vm, &s->sc[vo->cmask].a_sm); /* Set visibility */ + + vo->ofake = 1; /* Outside fake - don't plot vnets and don't use */ + /* for perceptual function extension. */ + vo->used = 1; /* Not a candidate for seeding */ + } + next_nco:; + CB_INC(nco); + } /* Next combination */ + } + + /* Create an initial vertex network */ + create_vtx_net(s); + + /* Fit the unbounded perceptual model to just the inside vertexes */ + if (s->pmod_init == 0) + init_pmod(s); + + /* Compute the nodes node and vertex lists */ + ofps_re_create_node_node_vtx_lists(s); + +#ifdef DUMP_STRUCTURE + printf("Done binit\n"); + dump_node_vtxs(s, 1); +// dump_node_vtxs2(s, "Done binit"); + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_SEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_SEED */ +} + +/* --------------------------------------------------- */ +/* Setup the perceptual lookup cache */ +static void +ofps_init_pcache(ofps *s) { + int i, e; + int di = s->di; + int gr, gres[MXPD]; + int tinp = s->tinp; + +#ifdef DEBUG + printf("Initializing perceptual lookup cache\n"); +#endif + + /* Choose a grid resolution that aims for aproximately TNPAGRID nodes per grid */ + if (tinp > 10000) + tinp = 10000; + gr = (int)(pow(tinp/TNPAGRID, 1.0/di) + 0.5); + gr |= 1; /* make it odd */ + + if (gr < TNPAGRIDMINRES) + gr = TNPAGRIDMINRES; + if (gr > TNPAGRIDMAXRES) + gr = TNPAGRIDMAXRES; + + if (s->verb) + printf("Perceptual cache resolution = %d\n",gr); + +#ifdef DEBUG + printf("Perceptual cache resolution = %d\n",gr); +#endif + + /* Create a rspl to cache the perceptual lookup */ + + if ((s->pcache = new_rspl(RSPL_NOFLAGS, s->di, s->di)) == NULL) + error("new_rspl failed"); + + for (e = 0; e < di; e++) + gres[e] = gr; + + s->pcache->set_rspl(s->pcache, RSPL_SET_APXLS, s->od, s->percept, NULL, NULL, gres, NULL, NULL); + + /* Hmm. Should we store the underlying ->percept & ->od somewhere before we overwrite it ? */ + s->percept = ofps_cache_percept; + s->od = s->pcache; + +} + +/* --------------------------------------------------- */ +/* Setup the acceleration grid structure and perceptual cache. */ +/* The grid is in device space, although it is used to find the point */ +/* with the smallest eperr. */ +/* (Note that ofps_cc_percept() can't be called on clipped values yet) */ +static void +ofps_init_acc1(ofps *s) { + int i, e; + int di = s->di; + int gres[MXPD]; + int tinp = s->tinp; + +#ifdef DEBUG + printf("Initializing accelleration array (1)\n"); +#endif + + /* Create acceleration grid array */ + + /* Choose a grid resolution that aims for aproximately TNPAGRID nodes per grid */ + if (tinp > 10000) + tinp = 10000; + s->agres = (int)(pow(tinp/TNPAGRID, 1.0/di) + 0.5); + if (s->agres < 1) + s->agres = 1; + + if (s->verb) + printf("Acceleration grid res = %d\n",s->agres); + +#ifdef DEBUG + printf("Acceleration grid res = %d\n",s->agres); +#endif + + /* Cell width in grid units */ + s->gw = 1.0/s->agres; + + /* Compute grid index multipliers */ + /* (We allocate an two extra rows for boundary cells to be looked up.) */ + for (s->gim[0] = 1, e = 1; e < di; s->gim[e] = s->gim[e-1] * (s->agres+2), e++) + ; + + /* Compute cell diagonal distance */ + s->gcd = sqrt((double)di * s->gw * s->gw); + + /* Compute number of cells in grid (with two extra rows) */ + for (s->nig = 1, e = 0; e < di; e++) + s->nig *= (s->agres+2); + + /* Allocate grid (with two extra rows) */ + if ((s->_grid = (acell *)malloc(sizeof(acell) * s->nig)) == NULL) + error ("ofps: malloc failed for acceleration grid"); + + /* Set pointer to base of grid without extra row */ + for (s->grid = s->_grid, e = 0; e < di; e++) + s->grid += s->gim[e]; + + /* Initialise grid (including extra gruard rows) */ + { + DCOUNT(co, MXPD, di, -1, -1, (s->agres+1)); + + i = 0; + DC_INIT(co); + while (!DC_DONE(co)) { + acell *cp = &s->_grid[i]; + unsigned int gflag = 0; + + for (e = 0; e < di; e++) { + if (co[e] < 0 || co[e] >= s->agres) + gflag = BOUND_GFLAG; + cp->co[e] = co[e]; /* Grid coordinate of base of cell */ + cp->p[e] = co[e] * s->gw; /* Device coord of base of cell */ + cp->cp[e] = (co[e] + 0.5) * s->gw; /* Device coord of center of cell */ + } + + cp->gflag = gflag; + cp->head = NULL; + cp->vhead = NULL; + + DC_INC(co); + i++; + } + } + s->gflag = 0; + + /* Create the neighbour offset list */ + + /* There are 3^di -1 neighbours for each cell */ + for (s->nacnl = 1, e = 0; e < di; s->nacnl *= 3, e++) + ; + s->nacnl--; + + if ((s->acnl = (int *)malloc(sizeof(int) * s->nacnl)) == NULL) + error ("ofps: malloc failed on acnl list"); + + /* Initialise list from cube */ + { + DCOUNT(co, MXPD, di, -1, -1, 2); + + i = 0; + DC_INIT(co); + while (!DC_DONE(co)) { + + /* check we're not at the center cell */ + for (e = 0; e < di; e++) { + if (co[e] != 0) + break; + } + if (e < di) { /* Not center cell */ + /* Compute offset */ + for (s->acnl[i] = 0, e = 0; e < di; e++) { + s->acnl[i] += co[e] * s->gim[e]; + } +//printf("~1 acnl[%d] for co %s = %d\n",i,pco(di,co),s->acnl[i]); + i++; + } + DC_INC(co); + } + } +} + +/* Init the grid location p[] and v[] values */ +static void +ofps_init_acc2(ofps *s) { + int i, e, di = s->di; + int k; + DCOUNT(co, MXPD, di, 0, 0, 2); + double maxratio, avgratio, noratio; + double aitters = 0.0; + +#ifdef DEBUG + printf("Initializing accelleration array (2)\n"); +#endif + + for (i = 0; i < s->nig; i++) { + acell *cp = &s->_grid[i]; + + /* Lookup perceptual base and center values */ + ofps_cc_percept(s, cp->v, cp->p); + ofps_cc_percept(s, cp->cv, cp->cp); + } + + /* Compute the worst case eperr from a corner to the center */ + maxratio = -1.0; + avgratio = noratio = 0.0; + for (i = 0; i < s->nig; i++) { + acell *cp = &s->_grid[i]; + double ratio; + double mov[MXPD]; + + if (cp->gflag == BOUND_GFLAG) + continue; + +#define ACELITERS 20 + for (k = 0; ; k++) { + double eperr_avg, eperr_min, eperr_max, no; + cp->eperr = 0.0; + + DC_INIT(co); + eperr_avg = no = 0.0; + eperr_min = 1e300; + eperr_max = -1.0; + while (!DC_DONE(co)) { + acell *np = &s->_grid[i]; + double eperr; + int j; + + /* Locate cell corner */ + for (j = 0, e = 0; e < di; e++) + j += co[e] * s->gim[e]; + np = &s->_grid[i + j]; + + /* eperr from that corner to center of this cell */ + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, np->v, np->p); + eperr_avg += eperr; + if (eperr > eperr_max) + eperr_max = eperr; + if (eperr < eperr_min) + eperr_min = eperr; + + no++; + + if (eperr > cp->eperr) + cp->eperr = eperr; + + DC_INC(co); + } + eperr_avg /= no; + + ratio = eperr_max/eperr_min; + + if (k >= ACELITERS || ratio < 1.2) { + avgratio += ratio; + noratio++; + if (ratio > maxratio) + maxratio = ratio; + + break; + + } else { + + /* Adjust the center position to minimuze range of eperr's */ + for (e = 0; e < di; e++) + mov[e] = 0.0; + +//printf("~1 cp was at %s\n",ppos(di,cp->cp)); + DC_INIT(co); + while (!DC_DONE(co)) { + acell *np = &s->_grid[i]; + double eperr, wf; + int j; + + /* Locate cell corner */ + for (j = 0, e = 0; e < di; e++) + j += co[e] * s->gim[e]; + np = &s->_grid[i + j]; + + /* Compose new center point from weighted corner points. */ + /* Weighting is proportional to eperr value */ + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, np->v, np->p); + + if (eperr < eperr_avg) { + /* Move away from corner */ + wf = (eperr_avg - eperr)/eperr_avg; +//printf("~1 eperr %f, avg %f, min %f, wf %f\n",eperr,eperr_avg,eperr_min,wf); + for (e = 0; e < di; e++) + mov[e] += wf * (cp->cp[e] - np->p[e]); + } else { + /* Move towards corner */ + wf = (eperr - eperr_avg)/eperr_avg; +//printf("~1 eperr %f, avg %f, max %f, wf %f\n",eperr,eperr_avg,eperr_max,wf); + for (e = 0; e < di; e++) + mov[e] += wf * (np->p[e] - cp->cp[e]); + } + + DC_INC(co); + } + for (e = 0; e < di; e++) { + mov[e] = 1.2 * mov[e] / no; + cp->cp[e] += mov[e]; + } + ofps_cc_percept(s, cp->cv, cp->cp); +//printf("~1 moving by %s to %s\n",ppos(di,mov),ppos(di,cp->cp)); + } + } + aitters += k; + + cp->eperr *= CELLMAXEPERRFF; /* Times the fudge factor */ + } + aitters /= s->nig; + + avgratio /= noratio; +#ifdef DEBUG + printf("Average acell eperr ratio = %f, maximum = %f, avg itters %f\n",avgratio,maxratio,aitters); + +#endif + + s->agrid_init = 1; +} + +/* Convert a location into an acceleration cell index */ +static int +ofps_point2cell(ofps *s, double *v, double *p) { + int i, e, di = s->di; + int agres = s->agres; + double pp[MXPD]; + + ofps_clip_point(s, pp, p); + + for (i = e = 0; e < di; e++) { + int t; + t = (int)floor(agres * pp[e]); + if (t < 0) + t = 0; + else if (t >= agres) + t = (agres-1); + i += s->gim[e] * t; + } + return i; +} + +/* Diagnostic: Return the grid coordinates */ +static void ofps_gridcoords(ofps *s, int *c, double *v, double *p) { + int i, e, di = s->di; + int agres = s->agres; + double pp[MXPD]; + + ofps_clip_point(s, pp, p); + + for (i = e = 0; e < di; e++) { + int t; + c[e] = (int)floor(agres * pp[e]); + if (c[e] < 0) + c[e] = 0; + else if (c[e] >= agres) + c[e] = (agres-1); + } +} + +/* Add a node to the spatial acceleration grid */ +/* Note that little more than the node perceptual value */ +/* may be valid when this is called. */ +static void +ofps_add_nacc(ofps *s, node *n) { + int pci; + acell *cp; + + if (n->ix < 0) + return; + + pci = ofps_point2cell(s, n->v, n->p); + cp = &s->grid[pci]; + n->n = cp->head; + if (cp->head != NULL) + cp->head->pn = &n->n; + cp->head = n; + n->pn = &cp->head; + n->pci = pci; + n->cell = cp; + +#ifdef SANITY_CHECK_CLOSEST + if (s->agrid_init) { + double eperr; + /* Check that the eperr to the center of the cell */ + /* is less than the worst case for that cell */ + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, n->v, n->p); + if (eperr > cp->eperr) { + warning("Sanity check ofps_add_nacc() node ix %d eperr %f > cell eperr %f",n->ix,eperr,cp->eperr); + printf("Sanity check ofps_add_nacc() node ix %d eperr %f > cell eperr %f\n",n->ix,eperr,cp->eperr); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("ofps_add_nacc cell eperr failed"); +#endif + } + } +#endif /* SANITY_CHECK_CLOSEST */ +} + +/* Remove a node from the spatial acceleration grid */ +static void +ofps_rem_nacc(ofps *s, node *n) { + if (n->ix < 0) + return; + if (n->pn != NULL) { /* If is on acceleration list, remove it */ + *n->pn = n->n; + if (n->n != NULL) + n->n->pn = n->pn; + } + n->pn = NULL; + n->n = NULL; +} + +/* Add a vertex to the spatial acceleration grid */ +static void +ofps_add_vacc(ofps *s, vtx *vx) { + int pci; + acell *cp; + + /* Normal spatial acceleration grid */ + pci = ofps_point2cell(s, vx->v, vx->p); + cp = &s->grid[pci]; + vx->n = cp->vhead; + if (cp->vhead != NULL) + cp->vhead->pn = &vx->n; + cp->vhead = vx; + vx->pn = &cp->vhead; + vx->pci = pci; + +#ifdef DEBUG + printf("Adding vertex no %d to spatial accelleration grid in cell %d\n",vx->no,pci); +#endif + +#ifdef SANITY_CHECK_CLOSEST + if (s->agrid_init) { + int e, di = s->di; + double eperr; + double p[MXPD], v[MXPD]; + + /* Check that the eperr to the center of the cell */ + /* is less than the worst case for that cell */ + + /* Clip point in case it lies outside the grid, */ + /* and would give an excessive eperr */ + for (e = 0; e < di; e++) { + p[e] = vx->p[e]; + if (p[e] < 0.0) + p[e] = 0.0; + else if (p[e] > 1.0) + p[e] = 1.0; + } + ofps_cc_percept(s, v, p); + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, v, p); + + if (eperr > cp->eperr) { + +//printf("~1 Cell ix %d co %s center %s (%s), vtx at %s (%s) clipped to %s (%s)\n",pci,pco(s->di,cp->co),ppos(di,cp->cp),ppos(di,cp->cv),ppos(di,vx->p),ppos(di,vx->v),ppos(di,p),ppos(di,v)); + warning("Sanity check ofps_add_vacc() vtx no %d eperr %f > cell eperr %f",vx->no,eperr,cp->eperr); + printf("Sanity check ofps_add_vacc() vtx no %d eperr %f > cell eperr %f\n",vx->no,eperr,cp->eperr); + +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("ofps_add_vacc cell eperr failed"); +#endif + } + } +#endif /* SANITY_CHECK_CLOSEST */ +} + +/* Add a vertex to the seeding groups */ +static void +ofps_add_vseed(ofps *s, vtx *vx) { + double oog; + int pci; + acell *cp; + +#ifdef DEBUG + printf("Adding vertex no %d to sorted binary tree\n",vx->no); +#endif + + /* Add the vertex to the sorted binary trees */ + if ((aat_ainsert(s->vtreep, (void *)vx)) == 0) + error("aat_ainsert vertex malloc failed"); + + /* Out of gamut vertexes are not candidates for seeds */ + if ((oog = ofps_oog(s, vx->p)) > COINTOL) { + vx->used = 1; +//printf("Setting used on vtx no %d, used %d, eserr %f, vm %s nsp %d oog by %e\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp, oog); + } + + if (vx->used == 0) { +#ifdef INDEP_SURFACE + /* Only pick full dimensional visible vertexes for seeding group, */ + /* since only they have a full-d error value. */ + if (sm_andtest(s, &s->sc[0].a_sm, &vx->vm) != 0) { +#endif +//printf("Adding (3) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + if ((aat_ainsert(s->vtrees[vx->nsp], (void *)vx)) == 0) + error("aat_ainsert vertex malloc failed"); +#ifdef INDEP_SURFACE + } else { +//printf("Not adding (2) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + } +#endif + } +//else printf("Not adding (3) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); +} + +/* Remove a vertex from the seeding groups */ +static void +ofps_rem_vseed(ofps *s, vtx *vx) { + +#ifdef DEBUG + printf("Removing vertex no %d from sorted binary tree\n",vx->no); +#endif + + /* Remove the vertex from the sorted binary tree */ + if ((aat_aerase(s->vtreep, (void *)vx)) == 0) + error("aat_aerase vertex failed to find vertex no %d (3)", vx->no); + + if (vx->used == 0) { +#ifdef INDEP_SURFACE + /* Only pick full dimensional visible vertexes for seeding group, */ + /* since only they have a full-d error value. */ + if (sm_andtest(s, &s->sc[0].a_sm, &vx->vm) != 0) { +#endif +//printf("Removing (4) vtx no %d, 0x%x, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx, vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + if ((aat_aerase(s->vtrees[vx->nsp], (void *)vx)) == 0) + error("aat_aerase vertex failed to find vertex no %d (4)", vx->no); +#ifdef INDEP_SURFACE + } else { +//printf("Not removing (1) vtx no %d, 0x%x, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + } +#endif + } +// else printf("Not removing (2) vtx no %d, 0x%x, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); +} + +/* Remove a vertex from the spatial acceleration grid */ +static void +ofps_rem_vacc(ofps *s, vtx *vx) { + + /* Remove from spatial acceleration grid */ + if (vx->pn != NULL) { /* If is on acceleration list, remove it */ + *vx->pn = vx->n; + if (vx->n != NULL) + vx->n->pn = vx->pn; + } + vx->pn = NULL; + vx->n = NULL; +} + +/* Clear the spatial acceleration grid */ +static void +ofps_reset_acc(ofps *s) { + int i; + + for (i = 0; i < s->nig; i++) { + acell *cp = &s->_grid[i]; + if (cp->gflag != BOUND_GFLAG) + cp->gflag = 0; + cp->head = NULL; + cp->vhead = NULL; + } + s->gflag = 0; +} + +/* --------------------------------------------------- */ + +/* Creat a randomized order list of pointers to the fixed points */ +static void +ofps_setup_fixed( +ofps *s, +fxpos *fxlist, /* List of existing fixed points */ +int fxno /* Number in fixed list */ +) { + int e, di = s->di; + int i, j; + + s->fnp = 0; + if (fxno == 0) + return; + + /* Allocate a list of pointers sufficient for all the fixed points */ + if ((s->ufx = (fxpos **)calloc(sizeof(fxpos *), fxno)) == NULL) + error ("ofps: malloc failed on pointers to fixed list"); + + /* Add each fixed point to the list */ + for (i = 0; i < fxno; i++) { + + /* Clip the fixed point */ + ofps_clip_point7(s, fxlist[i].p, fxlist[i].p); + + /* Comute perceptual attributes */ + s->percept(s->od, fxlist[i].v, fxlist[i].p); + + /* Skip any duplicate points, or Voronoi will get confused.. */ + for (j = 0; j < s->fnp; j++) { + for (e = 0; e < di; e++) { + if (fabs(s->ufx[j]->p[e] - fxlist[i].p[e]) > 1e-5) + break; /* Not a match */ + } + if (e >= di) + break; /* Is a match */ + } + if (j < s->fnp) + continue; /* Skip adding this point */ + + s->ufx[s->fnp++] = &fxlist[i]; + } + + /* Randomly shuffle the fixed points */ + for (i = 0; i < s->fnp; i++) { + fxpos *tp; + + j = i_rand(0, s->fnp-1); + + /* Swap the pointers */ + tp = s->ufx[i]; + s->ufx[i] = s->ufx[j]; + s->ufx[j] = tp; + } + s->tinp -= (fxno - s->fnp); + +} + +/* Seed the object with any fixed points */ +/* (I think this is only used if ofps is used to check the stats */ +/* on all the points. ) */ +/* Return NZ on failure */ +static int +ofps_add_fixed( +ofps *s +) { + int e, di = s->di; + int i, j, ii; + + /* Add fixed points if there are any */ + if (s->fnp == 0) + return 0; + + if (s->verb) + printf("Adding %d unique fixed points\n",s->fnp); + + for (i = 0; i < s->fnp; i++) { + node *p = s->n[i]; /* Destination for point */ + + /* Make sure that fixed point is within our gamut */ + ofps_clip_point(s, s->ufx[i]->p, s->ufx[i]->p); + + for (e = 0; e < di; e++) { /* copy device and perceptual coords */ + p->op[e] = p->p[e] = s->ufx[i]->p[e]; + p->v[e] = s->ufx[i]->v[e]; + } + + /* Count gamut surface planes it lies on */ + det_node_gsurf(s, p, p->p); + + p->fx = 1; /* is a fixed point */ + + /* Compute the Voronoi for it, and inc s->np */ + if (add_node2voronoi(s, s->np, 1)) { + /* In theory we could try adding points in a different order, */ + /* by resetting the voronoi, shuffling all the fixedpoints */ + /* and re-adding them again. */ + warning("Adding a fixed point failed to hit any vertexes, and no points to swap with!"); + return 1; + } + + if (s->verb) + printf("%cAdded fixed %d/%d",cr_char,i,s->fnp); fflush(stdout); + +#ifdef DUMP_STRUCTURE + printf("Done node %d\n",s->np); + dump_node_vtxs(s, 0); + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_SEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_SEED */ + } + + return 0; +} + +/* Seed the object with any fixed and movable incremental farthest points. */ +/* (We are only called if there is at leaset one movable point) */ +static void +ofps_seed(ofps *s) { + int e, di = s->di; + int ii, i, j, k, fc; + double rerr; + int needfirst = 0; /* Need a special first seed point (seems better without this ?) */ + int dofixed = 0; /* Do a fixed point next */ + int abortonfail = 0; /* Abort on failing to add fixed points (isn't always good ?) */ + int nsp = 0; /* Number of surface points */ + aat_atrav_t *aat_tr; + + if (s->verb) + printf("\n"); + + if ((aat_tr = aat_atnew()) == NULL) + error("aat_atnew returned NULL"); + + if (s->verb) { + printf("There are %d unique fixed points to add (%d total fixed points)\n",s->fnp, s->fxno); + printf("There are %d far spread points to add\n",s->tinp - s->fnp); + } + + if (!needfirst && s->fnp > 1) { /* There are fixed points to add */ + dofixed = s->fnp > 2 ? 2 : 1; + } + + /* Seed all the points. */ + /* (i is the node we're creating, j is the verbose interval count, */ + /* fc is the count of the fixed points added, ii is the movable count) */ + for (fc = j = i = ii = 0; i < s->tinp; i++, j++) { + node *p = s->n[i]; /* New node */ + double spref_mult; + + /* Compute current surface preference weighting */ + if (s->tinp - s->fnp <= 1) /* Prevent divide by zero */ + spref_mult = 0.0; + else + spref_mult = ii/(s->tinp - s->fnp - 1.0); + spref_mult = (1.0 - spref_mult) * s->ssurfpref + spref_mult * s->esurfpref; + + if (needfirst) { /* No initial fixed points, so seed the first */ + /* point as a special. */ + double min[MXPD], max[MXPD]; + + p->fx = 0; + + /* If there are no fixed points in the bulk, make the */ + /* first point such a point, to avoid pathology. */ + for (e = 0; e < di; e++) + p->p[e] = s->imin[e] + (s->imax[e] - s->imin[e]) * 1.0/4.141592654; + + /* Clip the new location */ + ofps_clip_point8(s, p->p, p->p); + + s->percept(s->od, p->v, p->p); +#ifdef DEBUG + printf("Creating first seed point (moveable %d out of %d)\n",i+1,s->tinp); +#endif + } else if (dofixed) { /* Setup to add a fixed point */ + +#ifdef DEBUG + printf("Adding fixed point %d out of %d\n",fc+1,s->fnp); +#endif + + /* (Fixed points are already clipped and have perceptual value) */ + for (e = 0; e < di; e++) { /* copy device and perceptual coords */ + p->p[e] = s->ufx[fc]->p[e]; + p->v[e] = s->ufx[fc]->v[e]; + } + + p->fx = 1; /* is a fixed point */ + + /* Count gamut surface planes it lies on */ + if (det_node_gsurf(s, p, p->p) != 0) + nsp++; + + } else { /* Setup to add a movable point */ + int k; + int sf = 0; + + double spref_mult; + double mx; + vtx *vx, *bvx; + double spweight[MXPD+1]; /* Surface preference weight table */ + double bspweight; /* Biggest weight */ + +#ifdef DEBUG + printf("Adding movable point %d out of %d\n",i+1,s->tinp); +#endif + + p->fx = 0; + + /* Compute current surface preference weighting */ + if (s->tinp - s->fnp <= 1) /* Prevent divide by zero */ + spref_mult = 0.0; + else + spref_mult = ii/(s->tinp - s->fnp - 1.0); + spref_mult = (1.0 - spref_mult) * s->ssurfpref + spref_mult * s->esurfpref; + + /* Until we use the next vertex, keep looking for a movable point */ + for (;;) { + +#ifdef NEVER /* DEBUG: Show the contents of each list */ + printf("All vertex list:\n"); + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + printf(" Vtx %d, used %d, eserr %f, weserr %f, vm %s\n",vx->no,vx->used,vx->eserr,vx->eserr * spweight[vx->nsp],psm(s,&vx->vm)); + } + + for (e = 0; e <= (di+1); e++) { + printf("Sorted tree vertex list for nsp %d :\n",e); + + for (vx = aat_atlast(aat_tr, s->vtrees[e]); vx != NULL; vx = aat_atprev(aat_tr)) { + printf(" Vtx %d, used %d, eserr %f, weserr %f, vm %s\n",vx->no,vx->used,vx->eserr,vx->eserr * spweight[vx->nsp],psm(s,&vx->vm)); + } + } +#endif + /* Compute the surface weighting multiplier for */ + /* each possible nsp + 1 */ + spweight[0] = 1.0; + + for (e = 1; e <= di; e++) + spweight[e] = spweight[e-1] * spref_mult; + + /* Locate the Voronoi vertex with the greatest distance to a sampling points */ + for (mx = -1.0, bvx = NULL, e = 0; e <= (di+1); e++) { + double weserr; + + /* Get largest eserr vertex for this nsp */ + if ((vx = aat_atlast(aat_tr, s->vtrees[e])) == NULL) + continue; + + weserr = vx->eserr * spweight[vx->nsp]; + +//printf("~1 considering vertex no %d, eserr %f, weserr %f\n",vx->no,vx->eserr,weserr); + if (weserr > mx) { + mx = weserr; + bvx = vx; + } + } +//if (bvx != NULL) printf("~1 got vertex no %d, eserr %f, weserr %f\n",bvx->no,bvx->eserr,mx); + +#ifdef SANITY_CHECK_SEED + /* Do exaustive search of candidate vertexes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double tweserr; + + tweserr = vx->eserr * spweight[vx->nsp]; + + if (vx->used == 0 && tweserr > (mx + 10.0 * NUMTOL) +#ifdef INDEP_SURFACE + /* Only pick full dimensional visible vertexes, */ + /* since only they have a full-d error value. */ + && sm_andtest(s, &s->sc[0].a_sm, &vx->vm) != 0 +#endif + ) { + warning("!!!!!! Sanity: Didn't pick largest eperr vtx no %d %f, picked %d %f instead !!!!!!",vx->no,tweserr,bvx->no,mx); + printf("!!!!!! Sanity: Didn't pick largest eperr vtx no %d %f, picked %d %f instead !!!!!!\n",vx->no,tweserr,bvx->no,mx); + mx = tweserr; + bvx = vx; + } + } +#endif /* SANITY_CHECK_SEED */ + + if (bvx == NULL) { /* We've failed to find a movable point */ + /* This could be because there are fixed points */ + /* at all candidate locations. */ + + if (fc < s->fnp) { /* Use a fixed point */ + /* (Fixed points are already clipped and have perceptual value) */ + for (e = 0; e < di; e++) { /* copy device and perceptual coords */ + p->p[e] = s->ufx[fc]->p[e]; + p->v[e] = s->ufx[fc]->v[e]; + } + + p->fx = 1; /* is a fixed point */ + + /* Count gamut surface planes it lies on */ + if (det_node_gsurf(s, p, p->p) != 0) + nsp++; + break; /* Go and use this point */ + } + error("ofps: assert, there are no vertexes to choose in initial seed\n"); + } + +#ifdef DEBUG + printf("Picking vertex no %d at %s with weserr %f, mask 0x%x\n",bvx->no,ppos(di,bvx->p),mx,bvx->pmask); +#endif + + /* Don't pick the vertex again */ + bvx->used = 1; +//printf("Removing (4) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",bvx->no,bvx->used,bvx->eserr,psm(s,&bvx->vm),bvx->nsp); + if ((aat_aerase(s->vtrees[bvx->nsp], (void *)bvx)) == 0) + error("aat_aerase vertex failed to find vertex no %d (5)", bvx->no); + + /* Add the new node */ + for (e = 0; e < di; e++) + p->p[e] = bvx->p[e]; + + /* Count gamut surface planes it lies on */ + if (det_node_gsurf(s, p, p->p) != 0) + nsp++; + +#ifdef RANDOM_PERTERB + /* Compute radius of closest real node to vertex */ + rerr = 1.0; + for (k = 0; k <= di; k++) { + double rads; + int ix = bvx->nix[k]; + node *np; + if (ix < 0) + break; /* Done */ + np = s->n[ix]; + for (rads = 0.0, e = 0; e < di; e++) { + double tt = bvx->p[e] - np->p[e]; + rads += tt * tt; + } + if (rads < rerr) + rerr = rads; + } + rerr = s->lperterb * sqrt(rerr); + if (rerr < 0.001) + rerr = 0.001; + + /* Add a random offset to the position, and retry */ + /* if it collides with an existing node or future fixed point */ + for (k = 0; k < 20; k++) { + int pci; /* Point list index */ + acell *cp; /* Acceleration cell */ + node *p1; + + for (e = 0; e < di; e++) + p->p[e] = bvx->p[e] + d_rand(-rerr, rerr); + + /* Confine node to planes vertex was on, */ + /* bit not if we're having trouble avoiding collissions */ + if (bvx->nsp > 0) + confineto_gsurf(s, p->p, p->sp, p->nsp); + + ofps_clip_point9(s, p->p, p->p); + + s->percept(s->od, p->v, p->p); + + pci = ofps_point2cell(s, p->v, p->p); /* Grid index of cell of interest */ + if ((cp = &s->grid[pci]) == NULL) + break; /* Nothing in cell */ + for (p1 = cp->head; p1 != NULL; p1 = p1->n) { + for (e = 0; e < di; e++) { + if (fabs(p->v[e] - p1->v[e]) > COINTOL) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Random offset node ix %d at %s collides with ix %d at %s - retry random %d\n",p->ix,ppos(di,p->p),p1->ix,ppos(di,p1->p),k); +#endif + break; /* Retry */ + } + } + if (p1 != NULL) /* Coincident */ + continue; + + /* Check movable point against fixed points that are yet to be added */ + /* (~~~ Ideally we should use an accelleration structure to check */ + /* for cooincidence rather than doing an exaustive search. ~~~) */ + if (fc < s->fnp) { /* There are more fixed points to add */ + int f; + + for (f = fc; f < s->fnp; f++) { + for (e = 0; e < di; e++) { + if (fabs(p->p[e] - s->ufx[f]->p[e]) > COINTOL) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Movable node ix %d at %s collides with fixed point %d at %s - retry movable %d\n",p->ix,ppos(di,p->p),f,ppos(di,s->ufx[f]->p),k); +#endif + break; + } + } + if (f >= s->fnp) + break; /* movable point is not cooincident */ + } else { + break; /* Not cooincident, so OK */ + } + } + if (k >= 20) { + /* This can happen if we didn't pick the absolute largest weserr, */ + /* and the vertex we ended up with is being confined to the same */ + /* location as an existing vertex by the planes is on. */ + /* (Why does it have an weperr > 0.0 then ????) */ + /* Give up on this point and chose another one. */ + continue; + +// error("ofps_seed: Assert, was unable to joggle cooincindent point"); + } +#else /* !RANDOM_PERTERB */ + /* Confine node to planes vertex was on */ + if (bvx->nsp > 0) + confineto_gsurf(s, p->p, p->sp, p->nsp); + + ofps_clip_point9(s, p->p, p->p); + + s->percept(s->od, p->v, p->p); +#endif /* !RANDOM_PERTERB */ + + /* Added this movable point, so chosen the next point */ + break; + } /* keep looking for a movable point */ + } + + /* We now have a first/fixed/moevable point to add */ + +/* hack test */ +//p->p[0] = d_rand(0.0, 1.0); +//p->p[1] = d_rand(0.0, 1.0); +//ofps_cc_percept(s, p->v, p->p); + + /* Establish original position */ + for (e = 0; e < di; e++) + p->op[e] = p->p[e]; + + /* Compute the Voronoi for it, and inc s->np */ + /* Fail if we get a position fail */ + if (add_node2voronoi(s, i, dofixed && abortonfail)) { + if (dofixed) { + /* Pospone adding this vertex */ + if ((s->fnp - fc) >= (s->tinp - i - 1)) { /* No room for moveable points */ +// error("Adding fixed point failed to hit any vertexes or posn. failed"); + abortonfail = 0; + } else + dofixed = 0; + } + if (needfirst) { + /* Hmm. The first seed point has failed. What should we do ? */ + error("Adding first seed point failed to hit any vertexes or posn. failed"); + } + + /* Skip this point */ + --i; + --j; + continue; + } + + /* Suceeded in adding the point */ + if (p->fx) { /* Fixed point */ + fc++; + dofixed--; + if ((s->fnp - fc) >= (s->tinp - i - 1)) { /* No room for moveable points */ + dofixed = s->fnp - fc; /* Do all the fixed */ + } + } else { /* Movable point */ + ii++; + if (fc < s->fnp) { /* There are more fixed points to add */ + dofixed = s->fnp - fc; + /* Add fixed 2 at a time to try and minimize the disruption */ + /* of the movable point edge priority */ + if (dofixed > 2) + dofixed = 2; + } + } + + if (s->verb && (j == 11 || i == (s->tinp-1))) { + printf("%cAdded %d/%d",cr_char,s->np,s->tinp); fflush(stdout); + j = 0; + } +#ifdef DUMP_STRUCTURE + printf("Done node %d\n",i); + dump_node_vtxs(s, 0); + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_SEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_SEED */ + + needfirst = 0; /* Must have done first */ + } +//printf("Number of gamut surface points = %d\n",nsp); + + aat_atdelete(aat_tr); + + if (s->verb) + printf("\n"); +} + +/* Recreate the Voronoi diagram with the current point positions */ +static void +ofps_redo_voronoi( +ofps *s +) { + vtx *vx, *nvx; + int i, j, k, e, di = s->di; + + /* Retry if we get a failure to add a point */ + for (k = 0; k < NINSERTTRIES; k++) { + + /* (~9 should think about smoothing the pre-conditioning lookup */ + /* if the number of tries is high. Add this to rspl.) */ + + /* Clear the voronoi nodes */ + node_clear(s, s->n[-2 * di - 2]); + for (j = -s->gnp; j < s->np; j++) + node_clear(s, s->n[j]); + + /* Delete the voronoi verticies */ + for (vx = s->uvtx; vx != NULL; vx = nvx) { + nvx = vx->link; + del_vtx(s, vx); + } + s->uvtx = NULL; + + /* Clear out the spatial acceleration grid */ + ofps_reset_acc(s); + + if (s->nv != 0) + warning("ofps: Assert, clear didn't leave us with 0 vertexes"); + + if (s->umid != NULL) + warning("ofps: Assert, clear didn't empty used midpoint list"); + + if (aat_asize(s->vtreep) != 0) + warning("ofps: Assert, clear didn't empty vertex tree"); + for (e = 0; e <= (di+1); e++) { + if (aat_asize(s->vtrees[e]) != 0) + warning("ofps: Assert, clear didn't empty vertex tree"); + } + + /* Set number of points in voronoi to zero */ + s->np = 0; + + /* Initialse the empty veronoi etc. */ + ofps_binit(s); + + s->posfailstp = 0; + + /* Add all points in again. */ + for (i = 0 ;i < s->tinp; i++) { /* Same order as before */ + + /* Compute the Voronoi for it (will add it to spatial accelleration grid) */ + /* and increment s->np */ + if (add_node2voronoi(s, i, 0)) { + + /* Hmm. Shuffle and retry the whole thing. */ + shuffle_node_order(s); + break; + } + + /* If it's not going well, re-shuffle and abort too */ + if (i > 10 && s->posfailstp/(1.0+i) > 0.2) { +//printf("~1 after node %d, posfailes = %d, prop %f\n",i,s->posfailstp, s->posfailstp/(1.0+i)); + /* Hmm. Shuffle and and retry the whole thing. */ + if (s->verb > 1) + warning("Too many nodes are failing to be inserted - reshuffling and re-starting\n"); + shuffle_node_order(s); + break; + } + +#ifdef DUMP_STRUCTURE + printf("Done node %d\n",i); + dump_node_vtxs(s, 0); +// ofps_re_create_node_node_vtx_lists(s); +// if ((s->optit+1) >= 4) +// { char buf[200]; sprintf(buf, "Itteration %d node ix %d",s->optit+1,s->np-1); dump_node_vtxs2(s, buf); } + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_RESEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_RESEED */ + } + if (i >= s->tinp) { +#ifdef DEBUG + if (k > 1) printf("Took %d retries\n",k-1); +#endif /* DEBUG */ + break; + } + /* Retry the whole thing */ + } + if (k >= NINSERTTRIES) + error("Failed to re-seed the veronoi after %d tries - too many node insertion failures ?",NINSERTTRIES); +} + +/* ----------------------------------------------------------- */ +/* Ideas for improving the accelleration: + + When there is no SUBD, then it is possible that the node + neighbourhood net (if it is kept up to date during seeding) + could be used to locate the closest node and then vertex. + (It can't be used for fixup, because the voronoi properties + aren't true during fixup.) + Starting at at the first node found using the spiral structure, + check all it's neigbours and if it's neighbour is closer to + the target, switch to it. If no neighbour is closer, + then that is the closest node. + The closest vertex is then connected to the closest node ? + +*/ + +#undef DEBUG_FCLOSE + +/* Given a node, locate all vertexes that it hits. */ +/* s->flag is assumed to be relevant for the given node. */ +/* Any hit vertexes are added to the s->nxh list. */ +/* s->vvchecks and s->nvcheckhits will be updated. */ +/* Return nz if vertexs were hit */ +/* (This only returns visible vertexes.) */ +static int ofps_findhit_vtxs(ofps *s, node *nn) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + vtx *vx; + double beperr, eperr; + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + int hit = 0; + + if (nn->ix < 0) + error("ofps_findhit_vtxs given gamut boudary node ix %d",nn->ix); + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findhit_vtxs() called before agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating a hit vtx to node at p = %s, v = %s\n", ppos(di,nn->p), ppos(di,nn->v)); +#endif + + /* Determine the largest eperr of any vertex */ + { + aat_atrav_t *aat_tr; + + beperr = 1e300; + + if ((aat_tr = aat_atnew()) == NULL) + error("aat_atnew returned NULL"); + + /* Find the largest vertex eperr visible to the node */ + for (vx = aat_atlast(aat_tr, s->vtreep); vx != NULL; vx = aat_atprev(aat_tr)) { +#ifdef INDEP_SURFACE + if (sm_vtx_node(s, vx, nn) == 0) + continue; +#endif /* INDEP_SURFACE */ + beperr = vx->eperr; + break; + } + aat_atdelete(aat_tr); + +#ifdef DEBUG_FCLOSE + printf("Largest eperr of any vertex = %f\n", beperr); +// fprintf(stderr,"Largest eperr of any vertex = %f\n", beperr); +#endif + } + + s->nvfschd += s->nv; /* Number of vertexes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any hit vertexes, or until */ + /* we run out of cells that could possibly be hits. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; +#else + cp->slist = slist; + slist = cp; +#endif + cp->gflag = s->gflag; /* Cell is on list to be searched */ + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + double ceperr; + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* Compute the smallest eperr possible in this cell, by computing the */ + /* eperr of the cell center to the node minus the estimated */ + /* largest eperr of any point within the cell to the center. */ + ceperr = ofps_comp_eperr(s, NULL, cp->v, cp->p, nn->v, nn->p); + eperr = ceperr - cp->eperr; + +//printf("~1 ceperr %f, cp->eperr %f, eperr %f, beperr %f\n",ceperr,cp->eperr,eperr,beperr); + /* If smallest possible eperr is larger than largest vertexe eperr */ + if (eperr > beperr) { +//printf("~1 skipping cell\n"); +#ifdef SANITY_CHECK_CLOSEST + /* Check all nodees in the cell anyway */ + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + int par = 0; + + if (vx->cflag == s->flag) + continue; +#ifdef INDEP_SURFACE + if (sm_vtx_node(s, vx, nn) == 0) + continue; +#endif /* INDEP_SURFACE */ + + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) + par = 1; + } + + eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); + + if (!par && (vx->eperr - eperr) > 0.0) { +//printf("~1 Node ix %d at %s (%s)\n Cell ix %d co %s center %s (%s),\n vtx no %d at %s (%s)\n",nn->ix, ppos(di,nn->p),ppos(di,nn->v),cp - s->grid,pco(s->di,cp->co),ppos(di,cp->cp),ppos(di,cp->cv),vx->no, ppos(di,vx->p),ppos(di,vx->v)); + warning("Sanity check ofps_findhit_vtxs() cell skip failed, hit on vtx no %d, eperr %f < vx->eperr %f, cell ix %d eperr %f, est min eperr %f",vx->no,eperr,vx->eperr,cp - s->grid,ceperr,ceperr - cp->eperr); + printf("Sanity check ofps_findhit_vtxs() cell skip failed, hit on vtx no %d, eperr %f < vx->eperr %f, cell ix %d eperr %f, est min eperr %f\n",vx->no,eperr,vx->eperr,cp - s->grid,ceperr,ceperr - cp->eperr); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest node cell skip failed"); +#endif + } + } +#endif /* SANITY_CHECK_CLOSEST */ + continue; /* Cell is not worth searching */ + } + + /* Search the cell */ + s->ncellssch++; + + /* For vertexes in this cell */ + for (vx = cp->vhead; vx != NULL; vx = vx->n) { +#ifdef DEBUG + printf("Checking vtx no %d\n",vx->no); +#endif + /* If the vertex has already been checked */ + if (vx->cflag == s->flag) + continue; + + if (vx->ofake) /* ofake vertexes can't be hit */ + continue; + +#ifdef INDEP_SURFACE + /* Only check for hit if the vertex is visible to the node */ + if (sm_vtx_node(s, vx, nn) == 0) { +# ifdef DEBUG + printf("Vertex no %d xmask 0x%x vm %s isn't visible to ix %d pmask 0x%x a_sm %s\n",vx->no,vx->cmask,psm(s,&vx->vm),nn->ix,nn->pmask,psm(s,&s->sc[nn->pmask].a_sm)); +# endif /* DEBUG */ + continue; + } +#endif /* INDEP_SURFACE */ + + vx->add = 0; + vx->del = 0; + vx->par = 0; + + s->vvchecks++; /* Checking a vertex */ + + /* Check if node is already parent to this vertex. */ + /* This only happens during fixups if the reposition fails and we */ + /* retain the vertex with the deleted vertex location (not currently */ + /* done), or by slim numerical margine, so ignore such hits. */ + /* We treat a parent as a hit node for the purposes of recursion, */ + /* and add it to a special list used to complete the vertex net. */ + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { +#ifdef DEBUG + printf("Vertex no %d has already got node ix %d\n",vx->no,nn->ix); +#endif + vx->par = 1; + } + } + + /* nba_eperr is assumed to be valid if vx->cflag == s->flag */ + vx->nba_eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); +#ifdef DEBUG + printf("Computing nba_eperr of %f for vtx no %d\n",vx->nba_eperr, vx->no); +#endif + /* See if the vertex eperr will be improved */ + if (!vx->par && (vx->eperr - vx->nba_eperr) > 0.0) { + s->nvcheckhits++; + hit = 1; + vx->del = 1; /* Mark for deletion */ + vx->nxh = s->nxh; /* Add vertex to list */ + s->nxh = vx; + vx->hflag = s->flag; +#ifdef DEBUG + printf("Vertex error improvement hit by %f (%f < %f)\n",vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + + if (vx->par) { + printf("Vertex no %d hit by its own parent ix %d\n",vx->no, nn->ix); + warning("Vertex no %d hit by its own parent ix %d",vx->no, nn->ix); + } +#endif + } +#ifdef DEBUG + else { /* If worse */ + printf("Vertex error not hit by %f (%f < %f)\n",vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + } +#endif + vx->cflag = s->flag; + + } /* Next vertex in cell */ + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical inner loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; +#else + nc->slist = slist; + slist = nc; +#endif + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } /* Next cell in current list */ +//printf("~1 don that search list\n"); + } /* Next list */ +//printf("~1 no more search lists\n"); + + return hit; +} + +#ifdef DEBUG_FCLOSE +#undef DEBUG_FCLOSE +#endif + + +#undef DEBUG_FCLOSE + +/* Given a vertex, locate the smallest eperr node. */ +/* Return NULL if none, and if ceperr is not NULL, set it to the */ +/* eperr to the returned node. */ +/* (This only returns visible nodes.) */ +static node *ofps_findclosest_node(ofps *s, double *ceperr, vtx *vx) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + double eperr, beperr = 1e300; /* eperr of closest node */ + node *bno = NULL; /* Closest node */ + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findclosest_node() called befor agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating closest node to vtx at p = %s, v = %s\n", ppos(di,vx->p), ppos(di,vx->v)); +#endif + + s->nnfschd += s->np; /* Number of nodes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any better nodees, or until */ + /* we run out of cells that could improve on the current best. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, vx->v, vx->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; +#else + cp->slist = slist; /* Add it to start of list */ + slist = cp; +#endif + cp->gflag = s->gflag; /* Cell is on list to be searched */ + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + double ceperr; + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* Compute the eperr of the cell center to the vtx minus the estimated */ + /* largest eperr of any point within the cell to the center. */ + ceperr = ofps_comp_eperr(s, NULL, cp->v, cp->p, vx->v, vx->p); + eperr = ceperr - cp->eperr; + + /* If the cell is worth searching */ + if (eperr < beperr) { + node *no; + + /* Search the cell */ + s->ncellssch++; + + for (no = cp->head; no != NULL; no = no->n) { + +#ifdef INDEP_SURFACE + /* Check if this node is visible to this vtx */ + if (sm_vtx_node(s, vx, no) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the node to the new vtx */ + eperr = ofps_comp_eperr(s, NULL, no->v, no->p, vx->v, vx->p); + if (eperr < beperr) { + bno = no; + beperr = eperr; +#ifdef DEBUG_FCLOSE + printf("Improved to node ix %d eperr\n",bno->ix,beperr); +#endif + } + } + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical ivxer loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; +#else + nc->slist = slist; /* Add it to start of list */ + slist = nc; +#endif + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } +#ifdef SANITY_CHECK_CLOSEST + /* Check all nodees in the cell anyway */ + else { + double teperr; + node *no; + + for (no = cp->head; no != NULL; no = no->n) { + +#ifdef INDEP_SURFACE + /* Check if this node is visible to this vtx */ + if (sm_vtx_node(s, vx, no) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the node to the new vtx */ + teperr = ofps_comp_eperr(s, NULL, no->v, no->p, vx->v, vx->p); + if (teperr < beperr) { + warning("Sanity check ofps_findclosest_node() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from node ix %d",eperr,ceperr,cp->eperr,teperr,no->ix); + printf("Sanity check ofps_findclosest_node() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from node ix %d\n",eperr,ceperr,cp->eperr,teperr,no->ix); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest node cell skip failed"); +#endif + } + } + } +#endif /* SANITY_CHECK_CLOSEST */ + + } /* Next cell in current list */ +#ifdef DEBUG_FCLOSE + printf("Finished ivxer loop because p 0x%x = NULL\n",cp); +#endif + } /* Next list */ +#ifdef DEBUG_FCLOSE + printf("Finished outer loop because slist 0x%x = NULL\n",slist); +#endif + +#ifdef DEBUG_FCLOSE + if (bno == NULL) + printf("Failed to find a closest node"); + else + printf("Returning best node ix %d, eperr %f\n",bno->ix,beperr); +#endif + +#ifdef SANITY_CHECK_CLOSEST + /* Use exaustive search */ + { + double ch_beperr = 1e300; /* Device distance squared of closest vertex */ + node *ch_bno = NULL; + for (i = 0; i < (s->np-1); i++) { + node *nn = s->n[i]; + double eperr; + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the node and the vertex */ + eperr = ofps_comp_eperr(s, NULL, nn->v, nn->p, vx->v, vx->p); + if (eperr < ch_beperr) { + ch_bno = nn; + ch_beperr = eperr; + } + } + + if (ch_bno != NULL && ch_beperr + 1e-3 < beperr) { + if (bno == NULL) { + warning("Sanity check ofps_findclosest_node() failed,\n found none, should be ix %d dist %f",ch_bno->ix,ch_beperr); + printf("Sanity check ofps_findclosest_node() failed,\n found none, should be ix %d dist %f\n",ch_bno->ix,ch_beperr); + } else { + warning("Sanity check ofps_findclosest_node() failed,\n found ix %d dist %f, should be ix %d dist %f",bno->ix,beperr,ch_bno->ix,ch_beperr); + printf("Sanity check ofps_findclosest_node() failed,\n found ix %d dist %f, should be ix %d dist %f\n",bno->ix,beperr,ch_bno->ix,ch_beperr); + } +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest node failed"); +#endif + } + } +#endif + + if (bno != NULL && ceperr != NULL) + *ceperr = beperr; + + return bno; +} + +/* ----------------------------------------------------------- */ + +#ifdef NEVER /* No longer used */ + +/* Given a node, locate the smallest eperr vertex. */ +/* Return NULL if none, and if ceperr is not NULL, set it to the */ +/* eperr to the returned vertex. */ +/* (This only returns visible vertexes.) */ +static vtx *ofps_findclosest_vtx(ofps *s, double *ceperr, node *nn) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + double eperr, beperr = 1e300; /* eperr of closest vertex */ + vtx *bvx = NULL; /* Closest vertex */ + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findclosest_vtx() called befor agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating closest vtx to node at p = %s, v = %s\n", ppos(di,nn->p), ppos(di,nn->v)); +#endif + + s->nvfschd += s->nv; /* Number of vertexes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any better vertexes, or until */ + /* we run out of cells that could improve on the current best. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; + cp->gflag = s->gflag; /* Cell is on list to be searched */ + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + double ceperr; + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* Compute the eperr of the cell center to the node minus the estimated */ + /* largest eperr of any point within the cell to the center. */ + ceperr = ofps_comp_eperr(s, NULL, cp->v, cp->p, nn->v, nn->p); + eperr = ceperr - cp->eperr; + + /* If the cell is worth searching */ + if (eperr < beperr) { + vtx *vx; + + /* Search the cell */ + s->ncellssch++; + + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + eperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (eperr < beperr) { + bvx = vx; + beperr = eperr; +#ifdef DEBUG_FCLOSE + printf("Improved to vtx no %d eperr\n",bvx->no,beperr); +#endif + } + } + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical inner loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } +#ifdef SANITY_CHECK_CLOSEST + /* Check all vertexes in the cell anyway */ + else { + double teperr; + vtx *vx; + + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + teperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (teperr < beperr) { + warning("Sanity check ofps_findclosest_vtx() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from vtx no %d",eperr,ceperr,cp->eperr,teperr,vx->no); + printf("Sanity check ofps_findclosest_vtx() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from vtx no %d\n",eperr,ceperr,cp->eperr,teperr,vx->no); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest vertex cell skip failed"); +#endif + } + } + } +#endif /* SANITY_CHECK_CLOSEST */ + + } /* Next cell in current list */ +#ifdef DEBUG_FCLOSE + printf("Finished inner loop because p 0x%x = NULL\n",cp); +#endif + } /* Next list */ +#ifdef DEBUG_FCLOSE + printf("Finished outer loop because slist 0x%x = NULL\n",slist); +#endif + +#ifdef DEBUG_FCLOSE + if (bvx == NULL) + printf("Failed to find a closest vertex"); + else + printf("Returning best vtx no %d, eperr %f\n",bvx->no,beperr); +#endif + +#ifdef SANITY_CHECK_CLOSEST + /* Use exaustive search */ + { + double ch_beperr = 1e300; /* Device distance squared of closest vertex */ + vtx *vx, *ch_bvx = NULL; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { /* Check all vertexes */ + double eperr; + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + eperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (eperr < ch_beperr) { + ch_bvx = vx; + ch_beperr = eperr; + } + } + + if (ch_bvx != NULL && ch_beperr + 1e-3 < beperr) { + if (bvx == NULL) { + warning("Sanity check ofps_findclosest_vtx() failed,\n found none, should be no %d dist %f",ch_bvx->no,ch_beperr); + printf("Sanity check ofps_findclosest_vtx() failed,\n found none, should be no %d dist %f\n",ch_bvx->no,ch_beperr); + } else { + warning("Sanity check ofps_findclosest_vtx() failed,\n found no %d dist %f, should be no %d dist %f",bvx->no,beperr,ch_bvx->no,ch_beperr); + printf("Sanity check ofps_findclosest_vtx() failed,\n found no %d dist %f, should be no %d dist %f\n",bvx->no,beperr,ch_bvx->no,ch_beperr); + } +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest vertex failed"); +#endif + } + } +#endif + + if (bvx != NULL && ceperr != NULL) + *ceperr = beperr; + + return bvx; +} + +/* Given a node, locate a vertex that it hits. */ +/* Return NULL if none, and if ceperr is not NULL, set it to the */ +/* eperr to the returned vertex. */ +/* (This only returns visible vertexes.) */ +static vtx *ofps_findhit_vtx(ofps *s, double *ceperr, node *nn) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + double eperr, beperr = 1e300; /* eperr of closest vertex */ + vtx *bvx = NULL; /* Closest vertex */ + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findhit_vtx() called befor agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating a hit vtx to node at p = %s, v = %s\n", ppos(di,nn->p), ppos(di,nn->v)); +#endif + + s->nvfschd += s->nv; /* Number of vertexes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any hit vertexes, or until */ + /* we run out of cells that could improve on the current best. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ + for (j = 0; j < s->nacnl; j++) { +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; + cp->gflag = s->gflag; /* Cell is on list to be searched */ + } + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* If the cell is worth searching */ + if (1) { + vtx *vx; + + /* Search the cell */ + s->ncellssch++; + + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + eperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (eperr < vx->eperr) { + bvx = vx; + beperr = eperr; +#ifdef DEBUG_FCLOSE + printf("Found hit vtx no %d eperr\n",bvx->no,beperr); +#endif + break; + } + } + if (vx != NULL) + break; + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical inner loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } + } /* Next cell in current list */ + } /* Next list */ + +#ifdef DEBUG_FCLOSE + if (bvx == NULL) + printf("Failed to find a hit vertex"); + else + printf("Returning hit vtx no %d, eperr %f\n",bvx->no,beperr); +#endif + + if (bvx != NULL && ceperr != NULL) + *ceperr = beperr; + + return bvx; +} + +#ifdef DEBUG_FCLOSE +#undef DEBUG_FCLOSE +#endif + +#endif /* NEVER */ + +/* ----------------------------------------------------------- */ + +/* Re-position the vertexes given the current point positions, */ +/* and fixup the veronoi. */ +static void +ofps_repos_and_fix_voronoi( +ofps *s +) { + int e, di = s->di; + int i, j, k; + node *nds[MXPD+1]; /* Real nodes of vertex */ + int ii; /* Number of real nodes */ + double ee[MXPD+1]; /* Per node estimated error */ + vtx *vx; + node *nn, *pp; + int nfuxups, l_nfuxups; /* Count of fixups */ + int csllow; /* Count since last low */ + int mxcsllow = 5; /* Threshold to give up */ + +#ifdef DEBUG + printf("Repositioning vertexes\n"); +#endif + + /* Re-position the vertexes to match optimized node positions */ + s->fchl = NULL; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + nodecomb nc; + + if (vx->ifake || vx->ofake) + continue; + + vx->p_eperr = vx->eperr; + + /* Pointers to real nodes. */ + for (ii = e = 0; e <= di; e++) { + if (vx->nix[e] >= 0) + nds[ii++] = s->n[vx->nix[e]]; + if (vx->nix[e] < -s->nbp) + error("ofps_repos_and_fix_voronoi() got fake node no %d comb %s fake %d",vx->no,pcomb(di,vx->nix),vx->ofake); + } + + /* Compute the current eperr at the vertex given the repositioned nodes, */ + /* to set acceptance threshold for repositioned vertex. */ + ofps_pn_eperr(s, NULL, ee, vx->v, vx->p, nds, ii); + nc.ceperr = ofps_eperr2(ee, ii); + + /* Setup to re-position the vertex */ + memset((void *)&nc, 0, sizeof(nodecomb)); + for (e = 0; e < di; e++) { + nc.nix[e] = vx->nix[e]; + nc.p[e] = vx->p[e]; + nc.v[e] = vx->v[e]; + } + nc.nix[e] = vx->nix[e]; + +#ifdef DEBUG + printf("Repositioning vertex no %d nodes %s at %s, ceperr %f\n",vx->no,pcomb(di,vx->nix),ppos(di,vx->p),nc.ceperr); +#endif + + /* We're about to change the position and eperr: */ + ofps_rem_vacc(s, vx); + ofps_rem_vseed(s, vx); + + if (position_vtx(s, &nc, 1, 1, 0) == 2) { + /* Just leave it where it was. Perhaps fixups will delete it */ + if (s->verb > 1) + warning("re_position_vtx failed for vtx no %d at %s",vx->no,ppos(di,vx->p)); + } else { +//printf("~1 moved from %s to %s\n",ppos(di,vx->p),ppos(di,nc.p)); + + for (e = 0; e < di; e++) { + vx->p[e] = nc.p[e]; + vx->v[e] = nc.v[e]; + } + vx->eperr = nc.eperr; + vx->eserr = nc.eserr; + } + + /* Count the number of gamut surfaces the vertex falls on */ + det_vtx_gsurf(s, vx); + + /* We've changed the position and eperr: */ + ofps_add_vacc(s, vx); + ofps_add_vseed(s, vx); + + /* Add all vertexes to the "to be checked" list */ + vx->fchl = s->fchl; /* Add vertex to the "to be checked" list */ + if (s->fchl != NULL) + s->fchl->pfchl = &vx->fchl; + s->fchl = vx; + vx->pfchl = &s->fchl; + vx->fflag = s->fflag; + vx->fupcount = 0; + vx->fuptol = NUMTOL; + } + +#ifdef DUMP_PLOT_BEFORFIXUP + printf("Before applying fixups:\n"); + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_BEFORFIXUP */ + + /* Now fixup the veroni. */ +#ifdef DEBUG + printf("Doing fixups:\n"); +#endif + + /* We loop until the check list is empty */ + l_nfuxups = 1e9; + csllow = 0; + while (s->fchl != NULL && csllow < mxcsllow) { + vtx *nvx; + + s->fflag++; /* Fixup round flag */ + s->nsvtxs = 0; + nfuxups = 0; + +#ifdef DEBUG + printf("\nFixup round %d%s\n",s->fflag, s->fchl == NULL ? "" : " fchl != NULL"); +#endif + + /* out of gamut, or whether the closest node to it */ + /* is not one of its parent nodes. */ + for (vx = s->fchl; vx != NULL; vx = nvx) { + double ceperr; /* eperr to closest node */ + int hit = 0; + + /* For each vertex on the check list, check if it is */ + nvx = vx->fchl; +#ifdef DEBUG + printf("Checking vtx no %d, fuptol %e\n",vx->no,vx->fuptol); +#endif + + vx->hnode = NULL; + nn = NULL; + /* Check if the vertex position is clipped, */ + /* and add fake boundary node if it is */ + /* For all the gamut boundary planes: */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + nn = s->n[-1-i]; + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; + } +#endif /* INDEP_SURFACE */ + + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + if (v > vx->fuptol) { + +#ifdef NEVER + /* This is an optimization: */ + /* Check whether nn is already a parent of the node */ + for (e = 0; e <= di; e++) { + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { + continue; /* It is */ + } +#endif + + /* Add all the vertexes parent nodes to the nearest nodes "add" list */ +#ifdef DEBUG + printf("Vertex no %d hit by boundary node ix %d by %e\n",vx->no,nn->ix,v); +#endif + hit = 1; + if (vx->hnode == NULL) { + vx->hnode = nn; + vx->hitmarg = 50.0 * v; + + if (s->nsvtxs >= s->_nsvtxs) { + s->_nsvtxs = 2 * s->_nsvtxs + 5; + if ((s->svtxs = (vtx **)realloc(s->svtxs, sizeof(vtx *) * s->_nsvtxs)) == NULL) + error("ofps: malloc failed on svtxs%d", s->_nsvtxs); + } + s->svtxs[s->nsvtxs]= vx; + vx->psvtxs = &s->svtxs[s->nsvtxs]; + s->nsvtxs++; + + } else if (50.0 * v > vx->hitmarg) { + vx->hnode = nn; + vx->hitmarg = 50.0 * v; + } +#ifdef DEBUG + printf("Added vtx no %d to node %d for fixup\n",vx->no,nn->ix); +#endif + } + } + + /* Or locate the nearest node to the vertex. */ + /* (This only returns visible nodes) */ + if ((nn = ofps_findclosest_node(s, &ceperr, vx)) != NULL) { + double errimp = vx->eperr - ceperr; + + /* See if it is closer than the parent nodes */ + if (errimp >= vx->fuptol) { /* It is */ + + /* Add the vertexe to the "to be fixed" list */ +#ifdef DEBUG + printf("Vertex no %d hit by node ix %d by %e\n",vx->no,nn->ix,errimp); +#endif + hit = 1; + + if (vx->hnode == NULL) { + vx->hnode = nn; + vx->hitmarg = errimp; + + if (s->nsvtxs >= s->_nsvtxs) { + s->_nsvtxs = 2 * s->_nsvtxs + 5; + if ((s->svtxs = (vtx **)realloc(s->svtxs, sizeof(vtx *) * s->_nsvtxs)) == NULL) + error("ofps: malloc failed on svtxs%d", s->_nsvtxs); + } + s->svtxs[s->nsvtxs]= vx; + vx->psvtxs = &s->svtxs[s->nsvtxs]; + s->nsvtxs++; + + } else if (errimp > vx->hitmarg) { + vx->hnode = nn; + vx->hitmarg = errimp; + } +#ifdef DEBUG + printf("Added node %d to vtx no %d fixup\n",nn->ix, vx->no); +#endif + } + } + + next_vtx:; + if (hit) { + vx->fupcount++; + vx->fuptol *= 2.0; + nfuxups++; + } + + /* Remove this vertex from the check list */ + if (vx->pfchl != NULL) { /* If is on fixup check list, remove it */ + *vx->pfchl = vx->fchl; + if (vx->fchl != NULL) + vx->fchl->pfchl = vx->pfchl; + } + vx->pfchl = NULL; + vx->fchl = NULL; + } + if (s->fchl != NULL) + error("Check list should be empty!"); + + if (nfuxups < l_nfuxups) { + l_nfuxups = nfuxups; + csllow = 0; + } else { + csllow++; + } + +#ifdef DEBUG + printf("\nAbout to fixup %d marked vertexes\n",nfuxups); +#endif + /* Smallest error to largest seems best, */ + /* probably because the closer nodes cut off the */ + /* further ones, reducing the number of redundant create/deletes */ +#define HEAP_COMPARE(A,B) ((A)->hitmarg < (B)->hitmarg) + HEAPSORT(vtx *, s->svtxs, s->nsvtxs); +#undef HEAP_COMPARE + + /* Fixup the back references after the sort */ + for (i = 0; i < s->nsvtxs; i++) { + vx = s->svtxs[i]; + vx->psvtxs = &s->svtxs[i]; + } + + /* For each vertex on the "to be fixed" list, */ + /* search for hits by the node starting at that vertex, */ + /* and recursively locate all the hit vertexes. */ + for (i = 0; i < s->nsvtxs; i++) { + + if ((vx = s->svtxs[i]) == NULL) { + continue; /* Vertex got deleted by a previous fix */ + } + nn = vx->hnode; + s->svtxs[i] = NULL; + vx->psvtxs = NULL; + + s->nvcheckhits = 0; /* Count number of vertexes hit by recursive check. */ + s->batch = NULL; /* Nothing in pending delete list */ + s->nup = NULL; /* Nothing in nodes to be updated list */ + s->flag++; /* Marker flag for adding this node */ + s->nxh = NULL; /* Nothing in nodes hit list */ + +#ifdef DEBUG + printf("\nFixing up node ix %d starting at vx no %d\n",nn->ix,vx->no); +// fprintf(stderr,"Fixing up node ix %d starting at vx no %d\n",nn->ix,vx->no); +#endif + /* Recursively search for all vertexes hit by the new node */ + /* Note that we don't care that this only finds connected hits, */ + /* since there should be a separate s->svtxs[] entry for a hit by this */ + /* node on a disconnected region. */ + ofps_check_vtx(s, nn, vx, 100000, 0); + +#ifdef DEBUG + printf("Fixing up node ix %d, %d vertexes hit by it\n",nn->ix,s->nvcheckhits); +#endif + + /* Number of nodes that would be checked by exaustive search */ + s->vvpchecks += s->nv; + + /* Now re-add the node to the veronoi */ + if (add_to_vsurf(s, nn, 1, 0) > 0) { + s->add_hit++; +#ifdef DUMP_PLOT_EACHFIXUP + printf("After adding node ix %d at %s to vurf\n",nn->ix,ppos(di,nn->p)); + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_EACHFIXUP */ + } else { +#ifdef DUMP_PLOT_EACHFIXUP + printf("Adding node ix %d at %s to vurf was miss\n",nn->ix,ppos(di,nn->p)); +#endif /* DUMP_PLOT_EACHFIXUP */ + s->fadd_mis++; + } + } + s->nsvtxs = 0; + } /* Loop until there are no more vertexes to check */ + +#ifdef DEBUG + printf("Done fixups s->fchl 0x%x == NULL or csllow %d >= %d\n",s->fchl,csllow,mxcsllow); +#endif + +#ifdef SANITY_CHECK_FIXUP + /* Check that no node other than a parent is closer to any vertex */ + if (check_vertex_closest_node(s)) { +#ifdef SANITY_CHECK_FIXUP_FATAL + error("!!!!!! Sanity: Fixup didn't work"); +#endif /* SANITY_CHECK_FIXUP_FATAL */ + } +#endif /* SANITY_CHECK_FIXUP */ + +#ifdef DEBUG + printf("Applied fixups\n"); +#endif +} + +/* --------------------------------------------------- */ +/* After seeding or re-positioning, create the node */ +/* neighbour node and vertex lists. */ +/* (Invalidates and deletes any midpoints) */ +static void ofps_re_create_node_node_vtx_lists(ofps *s) { + int i, e, di = s->di; + vtx *vx; + + /* for each node, clear its vertex list */ + for (i = -s->gnp; i < s->np; i++) { + node *p = s->n[i]; + p->nvv = 0; + } + + /* For each vertex, add it to each of its parent nodes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + for (e = 0; e <= di; e++) { + node *p = s->n[vx->nix[e]]; + node_add_vertex(s, p, vx); + } + } + + /* For each node, recompute its neighbourhood nodes */ + for (i = -s->gnp; i < s->np; i++) { + node *p = s->n[i]; + node_recomp_nvn(s, p); /* Recompute the nodes associated vertex nodes */ + } +} + +/* --------------------------------------------------- */ +/* Midpoints */ + +/* Finding midpoint location code using dnsqe() */ + +/* Context for callback */ +typedef struct { + ofps *s; + node *nds[2]; /* List of nodes */ +} mopt_cx; + +/* calculate the functions at x[] */ +int dnsq_mid_solver( /* Return < 0 on abort */ + void *fdata, /* Opaque data pointer */ + int n, /* Dimenstionality */ + double *x, /* Multivariate input values */ + double *fvec, /* Multivariate output values */ + int iflag /* Flag set to 0 to trigger debug output */ +) { + mopt_cx *cx = (mopt_cx *)fdata; + ofps *s = cx->s; + int e, di = s->di; + double pos[MXPD], sv[MXPD]; + double cee[2], teperr; + +//printf("~1 dnsq_solver got %d nodes and %d planes\n",cx->nn,cx->nsp); + + /* Compute pos as interpolation between node 0 and 1 */ + for (e = 0; e < di; e++) + pos[e] = cx->nds[0]->p[e] * (1.0 - x[0]) + cx->nds[1]->p[e] * x[0]; + + ofps_cc_percept(s, sv, pos); + + /* Get eperr */ + cee[0] = ofps_comp_eperr8(s, NULL, sv, pos, cx->nds[0]->v, cx->nds[0]->p); + cee[1] = ofps_comp_eperr8(s, NULL, sv, pos, cx->nds[1]->v, cx->nds[1]->p); + +//printf("~1 error = %f, %f", cee[0], cee[1]); + + teperr = 0.5 * (cee[0] + cee[1]); + + fvec[0] = teperr - cee[0]; + +// printf("dnsq_mid_solver returning %f from %f\n",fvec[0],x[0]); + + return 0; +} + +/* Create or re-create all the midpoints, given the vertexes are done. */ +static void +ofps_create_mids(ofps *s) { + int e, di = s->di; + int i, j, k; + double rerr; + int nsp = 0; /* Number of surface points */ + double dnsqtol = 1e-6; /* Solution tollerance to aim for */ + vopt_cx cx; + double fvec[1]; + int rv; + + cx.s = s; + +//printf("~1 creating mid points\n"); + /* Clear any existing midpoints */ + for (i = 0; i < s->tinp; i++) { + node *p = s->n[i]; + + if (p->ix < 0) + break; /* Done when we get to gamut boundary nodes */ + + for (j = 0; j < p->nvn; j++) { + if (p->mm[j] != NULL) { + del_mid(s, p->mm[j]); + p->mm[j] = NULL; + } + } + } + + /* For each node, make sure it and each neighbor node have a shared midpoint */ + for (i = 0; i < s->tinp; i++) { + node *p = s->n[i]; + + if (p->ix < 0) + break; /* Done when we get to gamut boundary nodes */ + + /* For each neighbor node, create midpoint */ + for (j = 0; j < p->nvn; j++) { + mid *mp; + node *p2; + double ee[2]; + + if (p->vn[j] < 0 || p->mm[j] != NULL) + continue; /* Gamut boundary or already got a midpoint */ + + /* Create a midpoint between node p->ix and p->vn[j] */ + p2 = s->n[p->vn[j]]; + mp = new_mid(s); + mp->refc++; + + p->mm[j] = mp; + for (k = 0; k < p2->nvn; k++) { + if (p2->vn[k] == p->ix) { + p2->mm[k] = mp; + mp->refc++; + break; + } + } + + mp->nix[0] = p->ix; + mp->nix[1] = p->vn[j]; +//printf("~1 creating midpoint %d between nodes %d %d\n",mp->no,p->ix,p->vn[j]); + + cx.nds[0] = p; + cx.nds[1] = p2; + mp->np = 0.5; + + /* Locate mid point */ + if ((rv = dnsqe((void *)&cx, dnsq_mid_solver, NULL, 1, &mp->np, + 0.2, fvec, 0.0, dnsqtol, 0, 0)) != 1 && rv != 3) { + error("ofps: Locating midpoint failed with %d",rv); + } + + for (e = 0; e < di; e++) + mp->p[e] = p->p[e] * (1.0 - mp->np) + p2->p[e] * mp->np; + ofps_cc_percept(s, mp->v, mp->p); + + /* Compute the eperr's for midpoint */ + ofps_pn_eperr(s, mp->ce, ee, mp->v, mp->p, cx.nds, 2); + mp->eperr = ofps_eperr2(ee, 2); + mp->eserr = ofps_eserr2(mp->ce, ee, 2); +//printf("~1 location %s (%s) eperr %f\n",ppos(di,mp->p), ppos(di,mp->v), mp->eperr); + } + } +} + +/* --------------------------------------------------- */ +/* Statistics: Compute serr stats. */ + +static void ofps_stats(ofps *s) { + int e, di = s->di; + int i, j; + double acnt; + vtx *vx; + mid *mp; + +//printf("~1 stats called\n"); + s->mn = 1e80; + s->mx = -1e80; + s->av = 0.0; + acnt = 0.0; + + /* Vertex stats */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double es; + + if (vx->ghost) /* Skip a ghost (coincident) vertex */ + continue; + +#ifdef INDEP_SURFACE + /* Ignore vertexes that aren't full dimension. */ + if (sm_andtest(s, &s->sc[0].a_sm, &vx->vm) == 0) + continue; +#endif + es = vx->eserr; + if (es >= 0.0 && es < s->mn) + s->mn = es; + if (es > s->mx) + s->mx = es; + s->av += es; + acnt++; + } + + s->av /= acnt; + + /* Midpoint/node stats */ + for (s->smns = 1e6, mp = s->umid; mp != NULL; mp = mp->link) { + + if (mp->nix[0] < 0 || mp->nix[1] < 0 + || mp->eserr < 0.0) + continue; /* Skip fake points */ + + if (mp->eserr < s->smns) { + s->smns = mp->eserr; + } + } + s->smns *= 2.0; /* Error distance between nodes is double error to midpoint */ +} + +/* --------------------------------------------------- */ +/* Support accessing the list of generated sample points */ + +/* Reset the read index */ +static void +ofps_reset(ofps *s) { + s->rix = 0; +} + +/* Read the next non-fixed point value */ +/* Return nz if no more */ +static int +ofps_read(ofps *s, double *p, double *v) { + int e; + + /* Advance to next non-fixed point */ + while(s->rix < s->np && s->n[s->rix]->fx) + s->rix++; + + if (s->rix >= s->np) + return 1; + + /* Return point info to caller */ + for (e = 0; e < s->di; e++) { + if (p != NULL) + p[e] = s->n[s->rix]->p[e]; + if (v != NULL) + v[e] = s->n[s->rix]->v[e]; + } + s->rix++; + + return 0; +} + +/* --------------------------------------------------- */ + +/* Compute more optimum location for node amongst the surrounding */ +/* verticies. The result is put in ->np[] and ->nv[]. */ +/* The main aim is to minimize the maximum eserr of any vertex, */ +/* but moving away from low midpoint eserr's improves the */ +/* convergence rate and improves the eveness of the result. */ +static void comp_opt(ofps *s, int poi, double oshoot, double sep_weight) { + node *pp; /* Node in question */ + int e, di = s->di; + double radsq = -1.0; /* Span/radius squared */ + double rad; + double sum; + int i; + int bi = 0, bj = 0; + + pp = s->n[poi]; /* Node in question */ + + /* Move towards vertex with highest eserr approach */ + if (pp->nvv > 0) { + double aerr1, werr1, berr1, cnt1; /* Average, worst, best eserr from vertexes */ + int weix1, beix1; /* Worst, best error vertex index */ + double ov1[MXPD]; /* Optimization vector towards largest vertex error */ + double aerr2, werr2, berr2, cnt2; /* Average, worst, best eserr from midpoints */ + int weix2, beix2; /* Worst, best error midpoint index */ + double ov2[MXPD]; /* Optimization vector away from smallest midpoint error */ + + +//printf("\n --------------------------------\n"); +//printf("~1 Optimizing ix %d, %f %f\n",poi,pp->p[0],pp->p[1]); + + /* Compute the average and locate the largest error from verticies */ + for (aerr1 = cnt1 = 0.0, werr1 = -1.0, berr1 = 1e80, i = 0; i < pp->nvv; i++) { + vtx *vp = pp->vv[i]; + +#ifdef INDEP_SURFACE + /* Ingnore vertexes that are not visible to this node. */ + if (sm_vtx_node(s, vp, pp) == 0) { + continue; + } +#endif + aerr1 += vp->eserr; + cnt1++; + +//printf("~1 Vertex no %d at %f %f serr = %f\n",vp->no,vp->p[0],vp->p[1],vp->eserr); + if (vp->eserr > werr1) { + werr1 = vp->eserr; + weix1 = i; + } + if (vp->eserr < berr1) { + berr1 = vp->eserr; + beix1 = i; + } + } + + if (cnt1 > 0.0 && werr1 > NUMTOL && berr1 > NUMTOL) { + double wbf, bbf; + double towards = 0.8; /* Amount to weight vector towards from closest */ + + /* Compute a blend factor that takes the current */ + /* location towards the worst vertex error and */ + /* away from the best */ + aerr1 /= cnt1; + wbf = towards * (werr1 - aerr1)/werr1; + bbf = (1.0 - towards) * (aerr1 - berr1)/berr1; +// wbf = towards * (werr1 - aerr1)/aerr1; +// bbf = (1.0 - towards) * (aerr1 - berr1)/aerr1; + + for (e = 0; e < di; e++) + ov1[e] = wbf * (pp->vv[weix1]->p[e] - pp->p[e]) + + bbf * (pp->p[e] - pp->vv[beix1]->p[e]); +//printf("~1 moved %f %f towards vtx no %d at %f %f\n",ov1[0],ov1[2],pp->vv[weix1]->no,pp->vv[weix1]->p[0],pp->vv[weix1]->p[1]); + } else { + for (e = 0; e < di; e++) + ov1[e] = 0.0; + } + + /* Compute the average and locate the smallest error from midpoints */ + for (aerr2 = cnt2 = 0.0, werr2 = 1e80, berr2 = -1.0, i = 0; i < pp->nvn; i++) { + mid *mp = pp->mm[i]; + node *on = s->n[pp->vn[i]]; /* Other node involved */ + + if (mp == NULL || mp->nix[0] < 0 || mp->nix[1] < 0) + continue; /* Must be a fake gamut boundary node */ + +#ifdef INDEP_SURFACE + /* Ingnore nodes of higher dimension */ + if ((pp->pmask & on->pmask) != pp->pmask) { + continue; + } +#endif + aerr2 += mp->eserr; + cnt2++; +//printf("~1 plane no %d from node ix %d serr = %f\n",mp->no,on->ix,mp->eserr); + if (mp->eserr < werr2) { + werr2 = mp->eserr; + weix2 = i; + } + if (mp->eserr > berr2) { + berr2 = mp->eserr; + beix2 = i; + } + } + + if (cnt2 > 0.0 && werr2 > NUMTOL && berr2 > NUMTOL) { + double wbf, bbf; + double away = 0.8; /* Amount to weight vector away from closest */ + + /* Compute a blend factor that takes the current */ + /* location away from the worst plane error */ + aerr2 /= cnt2; + wbf = away * (aerr2 - werr2)/werr2; + bbf = (1.0 - away) * (berr2 - aerr2)/berr2; +// wbf = away * (aerr2 - werr2)/aerr2; +// bbf = (1.0 - away) * (berr2 - aerr2)/aerr2; + + for (e = 0; e < di; e++) + ov2[e] = wbf * (pp->p[e] - pp->mm[weix2]->p[e]) + + bbf * (pp->mm[beix2]->p[e] - pp->p[e]); +//printf("~1 moved %f %f away from node ix %d at %f %f\n",ov2[0],ov2[1],pp->vn[weix2],pp->mm[weix2]->p[0],pp->mm[weix2]->p[1]); + } else { + for (e = 0; e < di; e++) + ov2[e] = 0.0; + } +//printf("~1 ov1 = %f %f, ov2 = %f %f, sep weight %f\n",ov1[0], ov1[1], ov2[0], ov2[1], sep_weight); + + /* Move the node by the sum of the two vectors */ + for (e = 0; e < di; e++) + pp->np[e] = pp->p[e] + (1.0 - sep_weight) * ov1[e] + sep_weight * ov2[e]; +//printf("~1 moved node %d by %f %f\n",pp->ix, (1.0 - sep_weight) * ov1[0] + sep_weight * ov2[0],(1.0 - sep_weight) * ov1[1] + sep_weight * ov2[1]); + } + +//printf("~1 check moved by %f %f\n",pp->np[0] - pp->p[0], pp->np[1] - pp->p[1]); + /* Apply overshoot/damping */ + for (e = 0; e < di; e++) + pp->np[e] = pp->p[e] + (pp->np[e] - pp->p[e]) * oshoot; +//printf("~1 after overshoot of %f got %f %f\n",oshoot,pp->np[0],pp->np[1]); + + /* Clip the new location */ + ofps_clip_point10(s, pp->np, pp->np); + +#if defined(KEEP_SURFACE) || defined(INDEP_SURFACE) + if (pp->nsp > 0) { + confineto_gsurf(s, pp->np, pp->sp, pp->nsp); + } +#endif + /* Update perceptual */ + s->percept(s->od, pp->nv, pp->np); /* Was clipped above */ + + /* Compute how far the point has moved */ + /* (?? maybe should change this to change in average or max eserr ??) */ + for (sum = 0.0, e = 0; e < di; e++) { + double tt = pp->np[e] - pp->p[e]; + sum += tt * tt; +//printf("~1 total motion = %f\n",sqrt(sum)); + } + if (sum > s->mxmvsq) /* Track maximum movement */ + s->mxmvsq = sum; +} + +static void +ofps_optimize( +ofps *s +) { + int maxits; + int transitters; + double transpow; + double oshoot, ioshoot, foshoot; + double sepw, isepw, fsepw; + double stoptol; + int e, di = s->di; + int i, j; + + /* Default is "Good" */ + maxits = OPT_MAXITS; + transitters = OPT_TRANS_ITTERS; + transpow = OPT_TRANS_POW; + ioshoot = OPT_INITIAL_OVERSHOOT; + foshoot = OPT_FINAL_OVERSHOOT; + isepw = OPT_INITIAL_SEP_WEIGHT; + fsepw = OPT_FINAL_SEP_WEIGHT; + stoptol = OPT_STOP_TOL; + +#ifdef OPT_MAXITS_2 + /* Option is "Fast" */ + if (s->good == 0) { + maxits = OPT_MAXITS_2; + transitters = OPT_TRANS_ITTERS_2; + transpow = OPT_TRANS_POW_2; + ioshoot = OPT_INITIAL_OVERSHOOT_2; + foshoot = OPT_FINAL_OVERSHOOT_2; + isepw = OPT_INITIAL_SEP_WEIGHT_2; + fsepw = OPT_FINAL_SEP_WEIGHT_2; + stoptol = OPT_STOP_TOL_2; + } +#endif /* OPT_MAXITS_2 */ + + oshoot = ioshoot; + for (s->optit = 0; s->optit < maxits; s->optit++) { /* Up to maximum number of itterations */ + vtx *vx; + double bf = 1.0; + int nvxhits; + double hratio, thresh; + int doinc = 0; + + s->mxmvsq = 0.0; + if (s->optit < transitters) + bf = s->optit/(double)transitters; + bf = pow(bf, transpow); + oshoot = (1.0 - bf) * ioshoot + bf * foshoot; + sepw = (1.0 - bf) * isepw + bf * fsepw; + + /* Compute optimized node positions */ + for (i = 0; i < s->tinp; i++) { + + if (s->n[i]->fx) + continue; /* Ignore fixed points */ + + comp_opt(s, i, oshoot, sepw); + } + + /* Then update their positions to the optimized ones */ + for (i = 0; i < s->tinp; i++) { + node *pp = s->n[i]; + + if (pp->fx) + continue; /* Ignore fixed points */ + + ofps_rem_nacc(s, pp); /* Remove from spatial accelleration grid */ + + for (e = 0; e < di; e++) { + pp->op[e] = pp->p[e]; /* Record previous position */ + pp->p[e] = pp->np[e]; /* Move to optimized location */ + pp->v[e] = pp->nv[e]; + } + ofps_add_nacc(s, pp); /* Add to spatial acceleration grid */ + } + + /* Make sure that the optimized nodes don't accidentaly collide */ + for (i = 0; i < s->tinp; i++) { + node *pp = s->n[i]; + + if (pp->fx) + continue; /* Ignore fixed points */ + + for (j = 0; j < 20; j++) { /* Retry until not cooincident */ + int pci; /* Point list index */ + acell *cp; /* Acceleration cell */ + node *p1; + pci = ofps_point2cell(s, pp->v, pp->p); /* Grid index of cell of interest */ + + cp = &s->grid[pci]; + for (p1 = cp->head; p1 != NULL; p1 = p1->n) { + if (p1 == pp) + continue; + for (e = 0; e < di; e++) { + if (fabs(pp->p[e] - p1->p[e]) > COINTOL) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Optimized node ix %d at %s collides with ix %d at %s - joggling it %d\n",pp->ix,ppos(di,pp->p),p1->ix,ppos(di,p1->p),i); + warning("Optimized node ix %d at %s collides with ix %d at %s - joggling it %d",pp->ix,ppos(di,pp->p),p1->ix,ppos(di,p1->p),i); +#endif + ofps_rem_nacc(s, pp); /* Remove from spatial accelleration grid */ + + /* Joggle it's position */ + for (e = 0; e < di; e++) { + if (pp->p[e] < 0.5) + pp->p[e] += d_rand(0.0, 1e-4); + else + pp->p[e] -= d_rand(0.0, 1e-4); + } + /* Ignore confine planes. Next itter should fix it anyway ? */ + ofps_clip_point10(s, pp->p, pp->p); + + /* Update perceptual (was clipped above) */ + s->percept(s->od, pp->v, pp->p); + + break; + } + } + if (p1 == NULL) + break; + } + if (j >= 20) + error("ofps_optimize: Assert, was unable to joggle cooincindent point"); + } + + /* Ideally the fixup method should create and delete fewer vertexes */ + /* than reseeding, hence always be faster, but in practice this doesn't */ + /* seem to be so. Perhaps this is because the fixups are being */ + /* done in a far from optimal order ? What this means is that often */ + /* for big movements reseeding will be faster. To get the best of both, */ + /* we try and estimate when the fixup method will break even with */ + /* reseeding, and switch over. */ + + /* Estimate how many vertexes will be hit by the move */ + nvxhits = ofps_quick_check_hits(s); + + /* Decide which way to go */ + thresh = 1.0/(di * di); + hratio = nvxhits/(double)s->nv; +//printf("~1 quick check of vertex hits = %d, ratio %f, threshold %f\n",nvxhits,hratio,thresh); + + /* Hmm. Re-seed seems to sometimes be slower than expected for > 3D, */ + /* so don't use it. */ + if (hratio < thresh && di < 4) { + doinc = 1; + } + +#ifdef FORCE_RESEED /* Force reseed after itteration */ + doinc = 0; +#else +# ifdef FORCE_INCREMENTAL /* Force incremental update after itteration */ + doinc = 1; +# endif +#endif + /* Incrementally update veronoi */ + if (doinc) { + + if (s->verb) + printf("Fixing up veronoi\n"); + + /* Re-position the vertexes, and fixup the veronoi */ + ofps_repos_and_fix_voronoi(s); + + /* Reseed the veronoi */ + } else { + + if (s->verb) + printf("Re-seeding\n"); + + /* remove nodes from the spatial acceleration grid. */ + for (i = 0; i < s->tinp; i++) { + node *pp = s->n[i]; + ofps_rem_nacc(s, pp); /* Remove from spatial accelleration grid */ + } + + /* And recompute veronoi, and add to spatial accelleration grid. */ + ofps_redo_voronoi(s); + } + ofps_re_create_node_node_vtx_lists(s); + ofps_create_mids(s); + + ofps_stats(s); + if (s->verb) { + printf("It %d: Maxmv = %f, MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f, %.1f secs.\n",s->optit+1,sqrt(s->mxmvsq),s->smns,s->mn,s->av,s->mx,(msec_time() - s->l_mstime) / 1000.0); +#ifdef STATS + printf("Current vtx %d, created %d, deleted %d, positioned %d\n", s->nv,s->nvtxcreated - s->l_nvtxcreated,s->nvtxdeleted - s->l_nvtxdeleted, s->positions - s->l_positions); + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + } + +#ifdef DUMP_STRUCTURE + dump_node_vtxs(s, 1); +// { char buf[200]; sprintf(buf, "After itteration %d",s->optit+1); dump_node_vtxs2(s, buf); } + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, wait, verticies */ +#endif /* DUMP_PLOT */ + +#ifdef SANITY_RESEED_AFTER_FIXUPS + /* For debugging, replace the incremental fixed up veronoi with */ + /* a from scratch one. */ + + if (s->verb) + printf("Re-seeding after fixup:\n"); + + /* Save the current incremental vertexes */ + save_ivertexes(s); + + ofps_redo_voronoi(s); + ofps_re_create_node_node_vtx_lists(s); + ofps_create_mids(s); + + ofps_stats(s); + if (s->verb) { + printf("It %d: Maxmv = %f, MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f, %.1f secs.\n",s->optit+1,sqrt(s->mxmvsq),s->smns,s->mn,s->av,s->mx,(msec_time() - s->l_mstime) / 1000.0); +#ifdef STATS + printf("Current vtx %d, created %d, deleted %d, positioned %d\n", s->nvtxcreated - s->l_nvtxcreated,s->nvtxdeleted - s->l_nvtxdeleted, s->positions - s->l_positions); + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + } + + /* Check that no node other than a parent is closer to any vertex */ + if (check_vertex_closest_node(s)) { + warning("Verify that re-seed leaves only parents closest to vertexes failed"); + } + + /* Check the incremental vertexes against the re-seeded vertexes */ + if (check_vertexes(s)) { + warning("Verify of incremental vertexes failed!"); + printf("Verify of incremental vertexes failed!\n"); + } else { + warning("Verify of incremental vertexes suceeded!"); + } +#ifdef DUMP_STRUCTURE + dump_node_vtxs(s, 1); +#endif +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, wait, verticies */ +#endif /* DUMP_PLOT */ +#endif /* SANITY_RESEED_AFTER_FIXUPS */ + + if (sqrt(s->mxmvsq) < stoptol) + break; + } +} + +/* ------------------------------------------------------------------------ */ +/* Main object creation/destruction */ + +/* Destroy ourselves */ +static void +ofps_del(ofps *s) { + int i, e, di = s->di; + + if (s->ufx != NULL) + free(s->ufx); + + /* Free our nodes */ + for (i = 0; i < s->np; i++) { + node_free(s, s->n[i]); + } + s->n -= s->gnp; /* Fixup offset */ + free(s->n); + free(s->_n); + + /* Any free vertexes */ + while (s->fvtx != NULL) { + vtx *p = s->fvtx; + s->fvtx = p->link; + free(p); + } + + /* Any other allocations */ + s->sob->del(s->sob); + if (s->combs != NULL) { + for (i = 0; i < s->_ncombs; i++) { + if (s->combs[i].v1 != NULL) + free(s->combs[i].v1); + if (s->combs[i].v2 != NULL) + free(s->combs[i].v2); + } + free(s->combs); + } + if (s->sc) + free(s->sc); + + if (s->svtxs != NULL) + free(s->svtxs); + + if (s->_grid != NULL) + free(s->_grid); + + if (s->acnl != NULL) + free(s->acnl); + + if (s->vtreep != NULL) + aat_adelete(s->vtreep); + + for (e = 0; e <= (di+1); e++) { + if (s->vtrees[e] != NULL) + aat_adelete(s->vtrees[e]); + } + + if (s->pcache != NULL) + s->pcache->del(s->pcache); + + free(s); +} + +/* Constructor */ +ofps *new_ofps( +int verb, /* Verbosity level, 1 = progress, 2 = warnings */ +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +int tinp, /* Total number of points to generate, including fixed */ +int good, /* 0 = fast, 1 = good */ +double dadaptation, /* Degree of adaptation to device characteristic 0.0 - 1.0 */ +double devd_wght, /* Device space weighting (if dad < 0) */ +double perc_wght, /* Perceptual space weighting (if dad < 0) */ +double curv_wght, /* Curvature weighting (if dad < 0) */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od /* context for Perceptual function */ +) { + return new_ofps_ex(verb, di, ilimit, NULL, NULL, tinp, good, + dadaptation, devd_wght, perc_wght, curv_wght, + fxlist, fxno, percept, od, 0, -1); +} + +/* Extended constructor */ +ofps *new_ofps_ex( +int verb, /* Verbosity level, 1 = progress, 2 = warnings */ +int di, /* Dimensionality of device space */ +double ilimit, /* Total ink limit (sum of device coords max) */ +double *imin, /* Ink limit - limit on min of p[], usually >= 0.0 (may be NULL) */ +double *imax, /* Ink limit - limit on min of p[], usually <= 1.0 (may be NULL) */ +int tinp, /* Total number of points to generate, including fixed */ +int good, /* 0 = fast, 1 = good */ +double dadaptation, /* Degree of adaptation to device characteristic 0.0 - 1.0 */ +double devd_wght, /* Device space weighting (if dad < 0) */ +double perc_wght, /* Perceptual space weighting (if dad < 0) */ +double curv_wght, /* Curvature weighting (if dad < 0) */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od, /* context for Perceptual function */ +int ntostop, /* Debug - number of points until diagnostic stop */ +int nopstop /* Debug - number of optimizations until diagnostic stop, -1 = not */ +) { + int i, e; + ofps *s; + long stime,ttime; + + stime = clock(); + + if ((s = (ofps *)calloc(sizeof(ofps), 1)) == NULL) + error ("ofps: malloc failed on new ofps"); + + if (di > MXPD) + error ("ofps: Can't handle di %d",di); + + s->verb = verb; + s->ntostop = ntostop; + s->nopstop = nopstop; + + if ((s->sob = new_sobol(di)) == NULL) + error ("ofps: new_sobol %d failed", di); + + if (s->verb) + printf("Degree of adaptation: %.3f\n", dadaptation); + + /* Set internal values explicitly */ + if (dadaptation < 0.0) { + s->devd_wght = devd_wght; + s->perc_wght = perc_wght; + s->curv_wght = curv_wght; + + /* Set values implicitly with adapation level */ + } else { + if (dadaptation > 1.0) + dadaptation = 1.0; + + /* Convert to internal numbers */ + s->perc_wght = ADAPT_PERCWGHT * dadaptation; + s->curv_wght = ADAPT_CURVWGHT * dadaptation * dadaptation; + s->devd_wght = 1.0 - s->perc_wght; + } + if (s->verb) + printf("Adaptation weights: Device = %.3f, Perceptual = %.3f, Curvature = %.3f\n", + s->devd_wght,s->perc_wght,s->curv_wght); + + s->di = di; + + if (tinp < fxno) /* Make sure we return at least the fixed points */ + tinp = fxno; + + s->fxno = fxno; /* Number of fixed points provided */ + s->tinp = tinp; /* Target total number of points */ + + /* Hack to workaround pathalogical case. At ilimit == di-2.0, we get > 32 bits */ + /* of mask for CMYK */ + if (di >= 3 + && ilimit >= (di-2.0 - 2 * ILIMITEPS) + && ilimit <= (di-2.0 + 2 * ILIMITEPS)) + ilimit = di-2.0 - 2 * ILIMITEPS; + + s->ilimit = ilimit; + + for (e = 0; e < di; e++) { + if (imin != NULL) + s->imin[e] = imin[e]; + else + s->imin[e] = 0.0; + + if (imax != NULL) + s->imax[e] = imax[e]; + else + s->imax[e] = 1.0; + } + + /* Compute an approximate half expected sample point spacing, */ + /* and setup seeding acceleration grid. */ + { + double vol = 1.0; + double eprange; + + for (e = 0; e < di; e++) + vol *= s->imax[e] - s->imin[e]; + + vol /= tinp; /* Approx vol per point */ + vol = pow(vol, 1.0/di); /* Distance per point */ + + s->surftol = SURFTOL * vol; +//printf("~1 surftol = %f\n",s->surftol); + } + +#ifdef STANDALONE_TEST + /* If no perceptual function given, use default */ + if (percept == NULL) { + s->percept = default_ofps_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } +#else + /* If no perceptual function given, use default */ +//warning("~1 new_ofps_ex() forcing default perceptual function"); + if (percept == NULL) { + s->percept = default_ofps_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } +#endif + + s->good = good; /* Fast/Good flag */ + s->lperterb = PERTERB_AMOUNT; +#ifdef OPT_MAXITS_2 + if (s->good == 0) + s->lperterb = PERTERB_AMOUNT_2; +#endif + s->ssurfpref = INITIAL_SURFACE_PREF; + s->esurfpref = FINAL_SURFACE_PREF; + + /* Init method pointers */ + s->reset = ofps_reset; + s->read = ofps_read; + s->stats = ofps_stats; + s->del = ofps_del; + + s->gnp = 2 * di + 1 + 2; /* Gamut boundary + inside/outside fake points */ + /* -1 to -2di-1 are fake boundary nodes indexes, */ + /* with -2di-1 being the ink limit boundary. */ + /* -2di-2 is the fake inside node. */ + /* -2di-3 is the fake outside node. */ + + /* Allocate the space for the target number of points */ + if ((s->_n = (node *)calloc(sizeof(node), s->gnp + s->tinp)) == NULL) + error ("ofps: malloc failed on sample nodes"); + if ((s->n = (node **)calloc(sizeof(node *), s->gnp + s->tinp)) == NULL) + error ("ofps: malloc failed on sample nodes"); + s->n += s->gnp; /* Allow -ve index for fake points */ + for (i = -s->gnp; i < s->tinp; i++) { + int bitp; + s->n[i] = &s->_n[i + s->gnp]; + s->n[i]->ix = i; + + bitp = 31 & (i + (i >> 4) + (i >> 8) + (i >> 12)); + s->n[i]->ixm = (1 << bitp); + } + + s->np = s->fnp = 0; + +#ifdef STATS + /* Save current counts to report stats after a pass */ + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + + /* Setup the eperr sorted trees */ + if ((s->vtreep = aat_anew(vtx_aat_cmp_eperr)) == NULL) + error("Allocating aat tree failed"); + + /* One sorted tree per number of surface planes */ + for (e = 0; e <= (di+1); e++) { + if ((s->vtrees[e] = aat_anew(vtx_aat_cmp_eserr)) == NULL) + error("Allocating aat tree failed"); + } + +#ifdef CACHE_PERCEPTUAL + ofps_init_pcache(s); +# endif /* CACHE_PERCEPTUAL */ + + /* Setup spatial acceleration grid */ + ofps_init_acc1(s); + + /* Initialse the empty veronoi etc. */ + ofps_binit(s); + + /* Setup spatial acceleration grid (2) */ + ofps_init_acc2(s); + + /* Setup the fixed points */ + ofps_setup_fixed(s, fxlist, fxno); + + if (fxno > 0 && tinp <= fxno) { /* There are no moveable points to create */ + + /* Add the fixed points */ + if (ofps_add_fixed(s)) { + s->del(s); + return NULL; + } + + if (s->verb && fxno > 0) { + ofps_stats(s); + printf("After fixed points: MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f\n",s->smns,s->mn,s->av,s->mx); + } + } + + if (tinp > fxno) { /* There are movable points to create */ + + /* Add the fixed points and create the moveable points */ + ofps_seed(s); + ofps_re_create_node_node_vtx_lists(s); + ofps_create_mids(s); + + ofps_stats(s); + if (s->verb) { + printf("After seeding points: MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f, %.1f secs\n",s->smns,s->mn,s->av,s->mx,(msec_time() - s->l_mstime) / 1000.0); + +#ifdef STATS + printf("Current vtx %d, created %d, deleted %d, positioned %d\n", s->nv,s->nvtxcreated - s->l_nvtxcreated,s->nvtxdeleted - s->l_nvtxdeleted, s->positions - s->l_positions); + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + } +# ifdef DUMP_STRUCTURE + printf("After seeding:\n"); + dump_node_vtxs(s, 1); +// dump_node_vtxs2(s, "After seeding"); +#else /* !DUMP_STRUCTURE */ +#ifdef SANITY_CHECK_CONSISTENCY + sanity_check(s, 1); +#endif +#endif /* !DUMP_STRUCTURE */ +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, No wait, no verticies */ +#endif /* DUMP_PLOT */ + +#ifdef DOOPT + /* Do the optimization */ + ofps_optimize(s); +#endif /* DOOPT */ + +# ifdef DUMP_STRUCTURE + printf("After optimization:\n"); + dump_node_vtxs(s, 1); +// dump_node_vtxs2(s, "After optimization"); +#else /* !DUMP_STRUCTURE */ +#ifdef SANITY_CHECK_CONSISTENCY + sanity_check(s, 1); +#endif +#endif /* !DUMP_STRUCTURE */ + ofps_stats(s); + if (s->verb) + printf("After optimization: MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f\n",s->smns, s->mn,s->av,s->mx); +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, wait, verticies */ +#endif /* DUMP_PLOT */ + } + + ofps_reset(s); /* Reset read index */ + +#if defined(DEBUG) || defined(STATS) + { + vtx *vx; + int novtx = 0; + int totvtxverts = 0; + int maxvtxverts = 0; + vtx **svtxs; /* Sorted vertexes by number of vertexes */ + + ttime = clock() - stime; + printf("Execution time = %f seconds\n",ttime/(double)CLOCKS_PER_SEC); + + /* Look at the vertexes */ + for (novtx = 0, vx = s->uvtx; vx != NULL; vx = vx->link, novtx++) + ; + + if ((svtxs = (vtx **)malloc(sizeof(vtx *) * novtx)) == NULL) + error ("ofps: malloc failed on vertex pointer list"); + + /* Look at the vertexes */ + for (novtx = 0, vx = s->uvtx; vx != NULL; vx = vx->link, novtx++) { + + svtxs[novtx] = vx; + + totvtxverts += vx->nnv; + if (vx->nnv > maxvtxverts) + maxvtxverts = vx->nnv; + } + +#define HEAP_COMPARE(A,B) ((A)->nnv > (B)->nnv) + HEAPSORT(vtx *, svtxs, novtx); +#undef HEAP_COMPARE + +// printf("Top 20 vertexes per vertex:\n"); +// for (i = 0; i < 20 && i < novtx; i++) { +// printf(" Vtx no %d, no vtxs = %d\n",svtxs[i]->no,svtxs[i]->nnv); +// } + + fprintf(stderr,"Average vertexes per vertex %.1f, max %d\n",totvtxverts/(double)novtx,maxvtxverts); + fprintf(stderr,"Average hit vertexes per add %.1f\n",s->nhitv/(double)s->nsurfadds,s->maxhitv); + fprintf(stderr,"Total number of vertex = %d\n",novtx); + fprintf(stderr,"Total vertex positions = %d\n",s->positions); + fprintf(stderr,"Total dnsqs = %d\n",s->dnsqs); + fprintf(stderr,"Total function calls = %d\n",s->funccount); + fprintf(stderr,"Average dnsqs/position = %.2f\n",s->dnsqs/(double)s->positions); + fprintf(stderr,"Average function calls/dnsq = %.1f\n",s->funccount/(double)s->dnsqs); + fprintf(stderr,"Maximum function calls/dnsq = %d\n",s->maxfunc); + fprintf(stderr,"Average function calls/sucessful dnsq = %.2f\n",s->sucfunc/(double)s->sucdnsq); + fprintf(stderr,"Average function calls/position = %.1f\n",s->funccount/(double)s->positions); + fprintf(stderr,"Maximum tries for dnsq sucess %d\n",s->maxretries); + fprintf(stderr,"Number of position_vtx failures %d\n",s->posfails); + fprintf(stderr,"Vertex hit check efficiency = %.1f%%\n",100.0 * (1.0 - s->vvchecks/(double)s->vvpchecks)); + fprintf(stderr,"Average accell cells searched = %.2f\n",s->ncellssch/(double)s->naccsrch); + fprintf(stderr,"add_to_vsurf hit rate = %.1f%%\n",100.0 * s->add_hit/(s->add_hit + s->add_mis)); +#ifdef DOOPT + fprintf(stderr,"fixup add_to_vsurf hit rate = %.1f%%\n",100.0 * s->fadd_hit/(s->fadd_hit + s->fadd_mis)); + fprintf(stderr,"Vertex closest search efficiency = %.1f%%\n",100.0 * (1.0 - s->nvschd/(double)s->nvfschd)); + fprintf(stderr,"Node closest search efficiency = %.1f%%\n",100.0 * (1.0 - s->nnschd/(double)s->nnfschd)); +#endif + + free(svtxs); + } +#endif + + return s; +} + +/* =================================================== */ + +#ifdef STANDALONE_TEST + +/* Graphics Gems curve */ +static double gcurve(double vv, double g) { + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + return vv; +} + + +static void sa_percept(void *od, double *p, double *d) { + double dd[2]; + + /* Default linear */ + p[0] = 100.0 * (dd[0] = d[0]); + p[1] = 100.0 * (dd[1] = d[1]); + + /* Normal non-linear test */ +// p[0] = 100.0 * gcurve(dd[0], -8.0); +// p[1] = 100.0 * gcurve(dd[1], 4.0); + + /* More extreme non-linear test */ + p[0] = 100.0 * gcurve(dd[0], -16.0); + p[1] = 100.0 * gcurve(dd[1], 8.0); + + /* An X break point to test curvature weighting */ +// if (dd[0] < 0.5) +// p[0] = 100.0 * 0.6 * dd[0]; +// else +// p[0] = 100.0 * (0.3 + 1.4 * (dd[0] - 0.5)); +// p[1] = 100.0 * dd[1]; + +// if (dd[0] < 0.0) +// dd[0] = 0.0; +// if (dd[1] < 0.0) +// dd[1] = 0.0; +// p[0] = 100.0 * pow(dd[0], 0.5); +// p[1] = 100.0 * pow(dd[1], 1.0); +// p[1] = 0.8 * p[1] + 0.2 * p[0]; + + /* One that causes dnsq failures due to ACCELL failure */ +// p[0] = gcurve(dd[0], -4.0); +// p[1] = gcurve(dd[1], 2.0); +// p[0] = 100.0 * gcurve(0.6 * p[0] + 0.4 * p[1], 2.0); +// p[1] = 100.0 * gcurve(0.1 * p[1] + 0.9 * p[1], -4.0); + +// p[0] = 100.0 * dd[0] * dd[0]; +// p[1] = 100.0 * dd[1] * dd[1]; +} + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int npoints = 55; + int ntostop = 0; + int nopstop = 0; + ofps *s; + fxpos fx[4]; /* Any fixed points */ + int nfx = 0; + + error_program = argv[0]; + + printf("Standalone test of ofps, args are: no. of points, default %d, points to skip before diag. plots, optim passes to skip\n",npoints); + + if (argc > 1) + npoints = atoi(argv[1]); + + if (argc > 2) + ntostop = atoi(argv[2]); + + if (argc > 3) + nopstop = atoi(argv[3]); + + fx[0].p[0] = 0.5; + fx[0].p[1] = 0.5; + + fx[1].p[0] = 0.145722; + fx[1].p[1] = 0.0; + + fx[2].p[0] = 1.0; + fx[2].p[1] = 0.104414; + + nfx = 0; + + /* Create the required points */ + s = new_ofps_ex(1, 2, 1.5, NULL, NULL, npoints, 1, +// s = new_ofps_ex(1, 2, 2.5, NULL, NULL, npoints, 1, + SA_ADAPT, SA_DEVD_MULT, SA_PERC_MULT, SA_INTERP_MULT, + fx, nfx, sa_percept, (void *)NULL, ntostop, nopstop); + +#ifdef DUMP_PLOT + printf("Device plot (with verts):\n"); + dump_image(s, 0, DO_WAIT, 1, DUMP_PLA, 1, -1); + printf("Device plot:\n"); + dump_image(s, 0, DO_WAIT, 0, 0, 1, -1); + printf("Perceptual plot (with verts):\n"); + dump_image(s, 1, DO_WAIT, 1, DUMP_PLA, 1, -1); + printf("Perceptual plot:\n"); + dump_image(s, 1, DO_WAIT, 0, 0, 1, -1); +#endif /* DUMP_PLOT */ + + s->del(s); + + return 0; +} + +#endif /* STANDALONE_TEST */ + +#define WIDTH 400 /* Raster size for debug plots */ +#define HEIGHT 400 + +/* Utility - return a string containing the di coord */ +static char *pco(int di, int *co) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + for (e = 0; e < di; e++) { + if (e > 0) + *bp++ = ' '; + sprintf(bp, "%d", co[e]); bp += strlen(bp); + } + return buf[ix]; +} + +/* Utility - return a string containing the di vector */ +static char *ppos(int di, double *p) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + for (e = 0; e < di; e++) { + double val = p[e]; + /* Make -0.00000000 turn into 0.000 for cosmetics */ + if (val < 0.0 && val >-1e-9) + val = 0.0; + if (e > 0) + *bp++ = ' '; + sprintf(bp, "%f", val); bp += strlen(bp); + } + return buf[ix]; +} + +/* Utility - return a string containing the di+1 combination */ +static char *pcomb(int di, int *n) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + for (e = 0; e <= di; e++) { + if (e > 0) + *bp++ = ' '; + sprintf(bp, "%d", n[e]); bp += strlen(bp); + } + return buf[ix]; +} + +/* Utility - return a string containing the eperr/eserr value */ +static char *peperr(double eperr) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + if (eperr >= 1e50) + sprintf(bp,"%s", "Big"); + else + sprintf(bp,"%f",eperr); + return buf[ix]; +} + +/* --------------------------------------------------------------- */ +#if defined(DEBUG) || defined(DUMP_PLOT_SEED) || defined(DUMP_PLOT) + +/* Dump the current point positions to a plot window file */ +static void +dump_image( + ofps *s, + int pcp, /* Do perceptual plot */ + int dwt, /* Do wait for a key */ + int dvx, /* Dump voronoi verticies and mid points */ + int dpla, /* Dump node planes */ + int ferr, /* Show final error rather than seeding error */ + int noi /* -1 for general state, node of interest for particular */ +) { + int i, j, k, e, di = s->di; + double minx, miny, maxx, maxy; + static double *x1a = NULL; /* Previous sample locations */ + static double *y1a = NULL; + static double *x2a = NULL; /* Current sample locations */ + static double *y2a = NULL; + static char *_ntext, **ntext; + static int _n3 = 0; /* Current Voronoi verticies */ + static double *x3a = NULL; + static double *y3a = NULL; + static plot_col *mcols = NULL; + static char *_mtext, **mtext; + int n3; + static double *x4a = NULL; /* plane vectors */ + static double *y4a = NULL; + static double *x5a = NULL; + static double *y5a = NULL; + static plot_col *ocols = NULL; + static int _o4 = 0; + int o4; + + if (pcp != 0) { /* Perceptual range */ + vtx *vx; + minx = miny = 1e60; + maxx = maxy = -1e60; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double v[MXPD]; + + if (vx->v[0] < minx) + minx = vx->v[0]; + if (vx->v[1] < miny) + miny = vx->v[1]; + if (vx->v[0] > maxx) + maxx = vx->v[0]; + if (vx->v[1] > maxy) + maxy = vx->v[1]; + } + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + +#ifdef NEVER + /* Expand the range a little */ + minx -= 0.1 * (maxx - minx); + maxx += 0.1/1.1 * (maxx - minx); + miny -= 0.1 * (maxy - miny); + maxy += 0.1/1.1 * (maxy - miny); +#endif + + if (x1a == NULL) { + if ((x1a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed x1a"); + if ((y1a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed ya1"); + if ((x2a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed x2a"); + if ((y2a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed y2a"); + if ((_ntext = (char *)malloc(s->tinp * 10 * sizeof(char))) == NULL) + error ("ofps: malloc failed _ntext"); + if ((ntext = (char **)malloc(s->tinp * sizeof(char *))) == NULL) + error ("ofps: malloc failed ntext"); + for (i = 0; i < s->tinp; i++) + ntext[i] = _ntext + i * 10; + } + + /* Add sample node location */ + for (i = 0; i < s->np; i++) { + node *p = s->n[i]; + + if (pcp != 0) { + double ov[MXPD]; + ofps_cc_percept(s, ov, p->op); + x1a[i] = ov[0]; + y1a[i] = ov[1]; + x2a[i] = p->v[0]; + y2a[i] = p->v[1]; + } else { + x1a[i] = p->op[0]; + y1a[i] = p->op[1]; + x2a[i] = p->p[0]; + y2a[i] = p->p[1]; + } + sprintf(ntext[i],"%d",p->ix); +// sprintf(ntext[i],"",p->ix); + } + + if (dvx) { + vtx *vx; + mid *mp; + node *p = NULL; +// double rgb0[3] = { 0.0, 0.5, 0.5 }; /* "cool" */ +// double rgb1[3] = { 1.0, 0.5, 0.0 }; /* "warm" */ + double rgb0[3] = { 0.0, 1.0, 0.0 }; /* "cool" */ + double rgb1[3] = { 1.0, 0.0, 0.5 }; /* "warm" */ + double mine, maxe; /* Min and max vertex eserr */ + + if (noi >= 0) + p = s->n[noi]; + + if (x3a == NULL) { /* Initial allocation */ + _n3 = s->np * 4; + if ((x3a = (double *)malloc(_n3 * sizeof(double))) == NULL) + error ("ofps: malloc failed x3a"); + if ((y3a = (double *)malloc(_n3 * sizeof(double))) == NULL) + error ("ofps: malloc failed y3a"); + if ((mcols = (plot_col *)malloc(_n3 * sizeof(plot_col))) == NULL) + error ("ofps: malloc failed mcols"); + if ((_mtext = (char *)malloc(_n3 * 10 * sizeof(char))) == NULL) + error ("ofps: malloc failed _mtext"); + if ((mtext = (char **)malloc(_n3 * sizeof(char *))) == NULL) + error ("ofps: malloc failed mtext"); + for (i = 0; i < _n3; i++) + mtext[i] = _mtext + i * 10; + } + + /* Compute min & max serr for each vertex */ + mine = 1e6; + maxe = -1e6; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + if (vx->ghost) + continue; + if (vx->eserr > maxe) + maxe = vx->eserr; + if (vx->eserr > NUMTOL && vx->eserr < mine) + mine = vx->eserr; + } + if ((maxe - mine) < 10.0) + maxe = mine + 1.0; + + /* Add mid points */ + for (n3 = 0, mp = s->umid; mp != NULL; mp = mp->link, n3++) { + + if (n3 >= _n3) { /* need more space */ + _n3 = 2 * _n3 + 5; + if ((x3a = (double *)realloc(x3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed x3a %d",_n3); + if ((y3a = (double *)realloc(y3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed y3a"); + if ((mcols = (plot_col *)realloc(mcols, _n3 * sizeof(plot_col))) == NULL) + error ("ofps: realloc failed mcols"); + if ((_mtext = (char *)realloc(_mtext, _n3 * 10 * sizeof(char))) == NULL) + error ("ofps: realloc failed _mtext"); + if ((mtext = (char **)realloc(mtext, _n3 * sizeof(char *))) == NULL) + error ("ofps: realloc failed mtest"); + for (i = 0; i < _n3; i++) + mtext[i] = _mtext + i * 10; + } + if (pcp != 0) { + x3a[n3] = mp->v[0]; + y3a[n3] = mp->v[1]; + } else { + x3a[n3] = mp->p[0]; + y3a[n3] = mp->p[1]; + } + + /* Show mid points in grey */ + mcols[n3].rgb[0] = 0.85; + mcols[n3].rgb[1] = 0.85; + mcols[n3].rgb[2] = 0.85; + + sprintf(mtext[n3],""); + sprintf(mtext[n3],"%d",mp->no); +// sprintf(mtext[n3],"%d",(int)(mp->eserr + 0.5)); + } + + /* Add Voronoi verticies */ + for (vx = s->uvtx; vx != NULL; vx = vx->link, n3++) { + + if (n3 >= _n3) { /* need more space */ + _n3 = _n3 * 2 + 5; + if ((x3a = (double *)realloc(x3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed x3a %d",_n3); + if ((y3a = (double *)realloc(y3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed y3a"); + if ((mcols = (plot_col *)realloc(mcols, _n3 * sizeof(plot_col))) == NULL) + error ("ofps: realloc failed mcols"); + if ((_mtext = (char *)realloc(_mtext, _n3 * 10 * sizeof(char))) == NULL) + error ("ofps: realloc failed _mtext"); + if ((mtext = (char **)realloc(mtext, _n3 * sizeof(char *))) == NULL) + error ("ofps: realloc failed mtext"); + for (i = 0; i < _n3; i++) + mtext[i] = _mtext + i * 10; + } + if (pcp != 0) { + x3a[n3] = vx->v[0]; + y3a[n3] = vx->v[1]; + } else { + x3a[n3] = vx->p[0]; + y3a[n3] = vx->p[1]; + } + + /* Show the vertexes as warm to cold, depending on their eserr */ + if (p == NULL) { + double bf; + + bf = (vx->eserr - mine)/(maxe - mine); + if (bf < 0.0) + bf = 0.0; + if (bf > 1.0) + bf = 1.0; + + for (e = 0; e < 3; e++) + mcols[n3].rgb[e] = bf * rgb1[e] + (1.0 - bf) * rgb0[e]; + +//printf("~1 serr = %f, color = %f %f %f\n",vx->eserr, mcols[n3].rgb[0], mcols[n3].rgb[1], mcols[n3].rgb[2]); +// sprintf(mtext[n3],""); +// sprintf(mtext[n3],"%d",(int)(vx->eserr + 0.5)); + +#ifndef NEVER /* Vertex no */ + sprintf(mtext[n3],"%d",vx->no); +#endif + +#ifdef NEVER /* Vertex no and eserr */ + if (vx->eserr >= 1e50) + sprintf(mtext[n3],"%d:Big",vx->no); + else + sprintf(mtext[n3],"%d:%d",vx->no,(int)(vx->eserr + 0.5)); +#endif + +#ifdef NEVER /* eserr */ + if (vx->eserr >= 1e50) + sprintf(mtext[n3],"Big"); + else + sprintf(mtext[n3],"%d",(int)(vx->eserr + 0.5)); +#endif + + /* Highlight the vertcies of interest */ + } else { + for (j = 0; j < p->nvv; j++) { + if (p->vv[j] == vx) + break; + } + if (j < p->nvv) { /* Vertex associated with node of interest */ + mcols[n3].rgb[0] = 0.1; + mcols[n3].rgb[1] = 0.9; + mcols[n3].rgb[2] = 0.9; + + sprintf(mtext[n3],"%d",(int)(vx->eserr + 0.5)); + + } else { + mcols[n3].rgb[0] = 0.82; /* default color */ + mcols[n3].rgb[1] = 0.59; + mcols[n3].rgb[2] = 0.0; + + sprintf(mtext[n3],""); + } + } + } +#ifdef DUMP_EPERR /* Create .tiff of eperr */ + if (s->np >= s->ntostop) { + + unsigned char pa[WIDTH * 3]; + char *name = "ofps.tif"; + int width = WIDTH; + int height = HEIGHT; + int x, y; + TIFF *tif; + double pos[MXPD], vpos[MXPD]; + double rgb_low[3] = { 0.0, 1.0, 0.0 }; /* "low error" */ + double rgb_high[3] = { 1.0, 0.0, 0.0 }; /* "high error" */ + + if ((tif = TIFFOpen(name, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",name); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + mine = 0.0; + + for (y = 0; y < height; y++) { + pos[1] = 1.0 - y/(height-1.0); + + /* Fill in pa[] with colors for this line */ + for (x = 0; x < width; x++) { + double ss; + unsigned char *dp; + double bf; + double beserr, eserr; + + dp = pa + x * 3; + pos[0] = x/(width-1.0); + dp[0] = dp[1] = dp[2] = 0; +//printf("~1 doing %d %d pos %f %f\n",x,y,pos[0],pos[1]); + + /* Lookup perceptual value at sample point location */ + ofps_cc_percept(s, vpos, pos); + + /* See if the sample is in gamut */ + for (ss = 0.0, e = 0; e < s->di; e++) { + if (pos[e] < s->imin[e] + || pos[e] > s->imax[e]) + break; + ss += pos[e]; + } + if (e < s->di || ss > (s->ilimit + ILIMITEPS)) { +//printf("~1 out of gamut\n"); + continue; + } + + /* We determine the eserr by evaluating eserr for */ + /* every node, and keeping the smallest. */ + /* (This could be speeded up by using nearest search function) */ + beserr = 1e80; + for (i = 0; i < s->np; i++) { + node *np = s->n[i]; + + eserr = ofps_comp_eperr9(s, NULL, vpos, pos, np->v, np->p); + if (eserr < beserr) + beserr = eserr; + } + bf = (beserr - mine)/(maxe - mine); +//printf("~1 beserr = %f, bf = %f\n",beserr,bf); + if (bf < 0.0) + bf = 0.0; + if (bf > 1.0) + bf = 1.0; + + for (e = 0; e < 3; e++) + dp[e] = (int)(255.0 * (bf * rgb_high[e] + (1.0 - bf) * rgb_low[e]) + 0.5); + + } + if (TIFFWriteScanline(tif, (tdata_t)pa, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); + } +#endif /* DUMP_EPERR */ + } + + /* Show veronoi planes by plotting the vertex network */ + if (dpla) { + vtx *vx1, *vx2; + + if (x4a == NULL) { + _o4 = s->tinp; + if ((x4a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((y4a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((x5a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((y5a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((ocols = (plot_col *)malloc(_o4 * sizeof(plot_col))) == NULL) + error ("ofps: malloc %d failed",_o4); + } + + /* Add normal planes then subd planes, so that subd are always on top */ + o4 = 0; +#ifdef INDEP_SURFACE + for (k = 0; k < 2; k++) /* Do two passes */ +#else + for (k = 0; k < 1; k++) +#endif + { + /* Add node planes */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + + /* Don't plot faces involving the fake inside or outside node */ + for (e = 0; e <= di; e++) { + if (vx1->nix[e] < -s->nbp) + break; + } + if (e <= di) + continue; + + for (j = 0; j < vx1->nnv; j++) { + vx2 = vx1->nv[j]; + + /* Don't plot faces involving the fake inside or outside node */ + for (e = 0; e <= di; e++) { + if (vx2->nix[e] < -s->nbp) + break; + } + if (e <= di) + continue; + +#ifdef INDEP_SURFACE + if (sm_andtest(s, &vx1->vm, &s->sc[0].a_sm) == 0 + || sm_andtest(s, &vx2->vm, &s->sc[0].a_sm) == 0) { /* Subd plane */ + if (k == 0) + continue; /* Doing non-zubd pass */ + } else { + if (k == 1) + continue; /* Doing subd pass */ + } +#endif + + if (o4 >= _o4) { /* need more space */ + _o4 *= 2; + if ((x4a = (double *)realloc(x4a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc x4a %d failed", _o4); + if ((y4a = (double *)realloc(y4a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc y4a %d failed", _o4); + if ((x5a = (double *)realloc(x5a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc x5a %d failed", _o4); + if ((y5a = (double *)realloc(y5a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc y5a %d failed", _o4); + if ((ocols = (plot_col *)realloc(ocols, _o4 * sizeof(plot_col))) == NULL) + error ("ofps: realloc y5a %d failed", _o4); + } + + if (pcp != 0) { + x4a[o4] = vx1->v[0]; + y4a[o4] = vx1->v[1]; + x5a[o4] = vx2->v[0]; + y5a[o4] = vx2->v[1]; + } else { + x4a[o4] = vx1->p[0]; + y4a[o4] = vx1->p[1]; + x5a[o4] = vx2->p[0]; + y5a[o4] = vx2->p[1]; + } + +#ifdef INDEP_SURFACE + /* Show the sub dimension outline in apricot */ + if (k == 1) { + ocols[o4].rgb[0] = 1.0; /* Apricot */ + ocols[o4].rgb[1] = 0.52; + ocols[o4].rgb[2] = 0.57; + } else +#endif + { + ocols[o4].rgb[0] = 0.5; /* Light Blue */ + ocols[o4].rgb[1] = 0.9; + ocols[o4].rgb[2] = 0.9; + } + o4++; + } + } + } + } + + if ((s->nopstop >= 0 && s->optit < s->nopstop) || s->np < s->ntostop) + dwt = 0; + + /* Plot the vectors */ + do_plot_vec2(minx, maxx, miny, maxy, + x1a, y1a, x2a, y2a, ntext, s->np, dwt, + x3a, y3a, mcols, mtext, dvx ? n3 : 0, + x4a, y4a, x5a, y5a, ocols, dpla ? o4 : 0); + +} + +#endif /* DEBUG || DUMP_PLOT */ + +/* ------------------------------------------------------------------- */ +#ifdef SANITY_RESEED_AFTER_FIXUPS + +/* Save the current used vertexes to the i_uvtx list, */ +/* so that they can be verified against the re-seeded vertexes */ +static void save_ivertexes(ofps *s) { + vtx *vx, *nvx; + + s->i_uvtx = NULL; + + for (vx = s->uvtx; vx != NULL; vx = nvx) { + nvx = vx->link; + + /* Remove the vertex from used and other lists */ + del_vtx1(s, vx); + + /* Add it to the i_uvtx list */ + vx->link = s->i_uvtx; + s->i_uvtx = vx; + } +} + +/* Check the incremental vertexes against the re-seeded vertexes */ +static int check_vertexes(ofps *s) { + int i, j, e, k, di = s->di; + vtx *v1, *v2; + int fail = 0; + + printf("Verifying incremental vertexes against re-seeded:\n"); + + /* For each reference (re-seeded) vertex */ + for (v1 = s->uvtx; v1 != NULL; v1 = v1->link) { + + /* Locate the equivalent incremental vertex */ + for (v2 = s->i_uvtx; v2 != NULL; v2 = v2->link) { + for (e = 0; e <= di; e++) { + if (v1->nix[e] != v2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (v2 == NULL) { + printf("Missing vertex no %d comb %s\n",v1->no,pcomb(di,v1->nix)); + fail = 1; + continue; + } + + /* Check the vertex location */ + for (e = 0; e < di; e++) { + if (fabs(v1->p[e] - v2->p[e]) > 1e-5) { + break; + } + } + if (e < di) { + printf("Vertex no %d (%d) comb %s in different location %s, should be %s\n",v1->no,v2->no,pcomb(di,v1->nix),ppos(di,v2->p),ppos(di,v1->p)); + fail = 1; + } + /* Check the eserr */ + if (fabs(v1->eserr - v2->eserr) > 1e-3) { + printf("Vertex no %d (%d) comb %s has different eserr %f, should be %f\n",v1->no,v2->no,pcomb(di,v1->nix),v2->eserr,v1->eserr); + fail = 1; + } + + /* Check setmask */ + if (!_sm_equal(s, &v1->vm, &v2->vm)) { + printf("Vertex no %d (%d) comb %s has different vm %s, should be %s\n",v1->no,v2->no,pcomb(di,v1->nix),psm(s,&v2->vm),psm(s,&v1->vm)); + fail = 1; + } + + /* Check that the vertex nets are the same */ + for (i = 0; i < v1->nnv; i++) { + vtx *vv1 = v1->nv[i]; + + for (j = 0; j < v2->nnv; j++) { + vtx *vv2 = v2->nv[j]; + + for (e = 0; e <= di; e++) { + if (vv1->nix[e] != vv2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (j >= v2->nnv) { + printf("Vertex no %d comb %s, i_ missing neighbour no %d comb %s\n",v1->no,pcomb(di,v1->nix),vv1->no,pcomb(di,vv1->nix)); + fail = 1; + } + } + for (j = 0; j < v2->nnv; j++) { + vtx *vv2 = v2->nv[j]; + + for (i = 0; i < v1->nnv; i++) { + vtx *vv1 = v1->nv[i]; + + for (e = 0; e <= di; e++) { + if (vv1->nix[e] != vv2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (i >= v1->nnv) { + printf("Vertex no %d comb %s, i_ extra neighbour no (%d) comb %s\n",v1->no,pcomb(di,v1->nix),vv2->no,pcomb(di,vv2->nix)); + fail = 1; + } + } + } + + /* For each incremental vertex, check that there is a corresponding re-seeded vertex */ + for (v2 = s->i_uvtx; v2 != NULL; v2 = v2->link) { + + for (v1 = s->uvtx; v1 != NULL; v1 = v1->link) { + for (e = 0; e <= di; e++) { + if (v1->nix[e] != v2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (v1 == NULL) { + printf("Extra vertex no (%d) comb %s\n",v2->no,pcomb(di,v2->nix)); + fail = 1; + } + } + + if (fail) + printf("Failed to verify incremental vertexes against re-seeded:\n"); + else + printf("Successfully verified incremental vertexes against re-seeded\n"); + + return fail; +} + +#endif /* SANITY_RESEED_AFTER_FIXUPS */ + +/* ------------------------------------------------------------------- */ +/* Do an exaustive, very slow check for missing vertexes */ +/* + This may be really, really, really slow. + + For every possible combination of di+1 nodes, + locate the corresponding vertex. If it is + locatable, check that no other node is closer to it. + If it meets these conditions, then check that it is in the veronoi surface. + */ +static void check_for_missing_vertexes(ofps *s) { + int e, di = s->di; + vtx *vx; + COMBO(co, MXPD+1, di+1, s->np + s->nbp); /* di-1 out of neighbor nodes combination counter */ + nodecomb vv; + int lsc = -100; + int isok = 1; + + printf("Doing exaustive check for missing vertexes:\n"); + + /* Mark all the vertexes so that we can tell if any are missed. */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + vx->sch = 0; + } + + CB_INIT(co); + while (!CB_DONE(co)) { + int rl = 0; + + memset((void *)&vv, 0, sizeof(nodecomb)); + for (e = 0; e <= di; e++) { + vv.nix[e] = co[e] - s->nbp; + if (vv.nix[e] >= 0) + rl = 1; + } + if (rl == 0) + goto next_comb; /* No real nodes */ + + vv.vv = NULL; + vv.ceperr = 1e100; + + sort_nix(s, vv.nix); + + printf("Comb %s\n",pcomb(di, vv.nix)); + if (lsc != vv.nix[di]) { + fprintf(stderr,"digit %d\n",vv.nix[di]); + lsc = vv.nix[di]; + } + + if (position_vtx(s, &vv, 0, 0, 0) == 0) { + int ix; + double eperr; + node *nn; + + printf(" Located at %s (%s), eperr %f\n",ppos(di,vv.p),ppos(di,vv.v),vv.eperr); + + /* Check that the point is not out of gamut */ + if (ofps_in_dev_gamut(s, vv.p, NULL) < -s->surftol) { + printf(" vertex is out of gamut\n"); + goto not_valid; + } + + /* Check that no other vertex is closer */ + for (ix = 0; ix < s->np; ix++) { + for (e = 0; e <= di; e++) { + if (vv.nix[e] == ix) + break; + } + if (e <= di) + continue; /* Is a parent */ + + nn = s->n[ix]; + eperr = ofps_comp_eperr(s, NULL, nn->v, nn->p, vv.v, vv.p); + + printf(" eperr to ix %d is %f\n",nn->ix,eperr); + if (eperr < vv.eperr) { + printf("vertex is closer to node ix %d\n",nn->ix); + break; + } + } + if (ix >= s->np) { + + printf("Point %s is valid\n",pcomb(di,vv.nix)); + + /* see if we've created it */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + + for (e = 0; e <= di; e++) { + if (vx->nix[e] != vv.nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (vx == NULL) { + printf("Can't find vertex %s at %s (%s)\n",pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + fprintf(stderr,"Can't find vertex %s at %s (%s)\n",pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + isok = 0; + } else { + vx->sch = 1; + printf("Found vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + fprintf(stderr,"Found vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + } + } + not_valid:; + } else { + printf(" Failed to locate %s\n",pcomb(di,vv.nix)); + } + next_comb:; + CB_INC(co); + } + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + if (vx->sch == 0) { + for (e = 0; e <= di; e++) { + if (vx->nix[e] < -s->nbp) /* involves inside or outside fake point */ + break; + } + if (e <= di) + continue; /* Ignore */ + printf("Extra vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vx->nix),ppos(di,vx->p),ppos(di,vx->v)); + fprintf(stderr,"Extra vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vx->nix),ppos(di,vx->p),ppos(di,vx->v)); + isok = 0; + } + } + if (isok) { + printf("Check for missing veftexes is OK\n"); + fprintf(stderr,"Check for missing veftexes is OK\n"); + } else { + printf("Check for missing veftexes FAILED\n"); + fprintf(stderr,"Check for missing veftexes FAILED\n"); + } +} + +/* ------------------------------------------------------------------- */ +/* Check the veronoi to check that no node other than the parent */ +/* node is closer to any vertex. */ +/* return nz if there is a problem */ +static int check_vertex_closest_node(ofps *s) { + int i, e, di = s->di; + node *nn, *pp; + vtx *vx; + + /* Check that no node other than a parent is closer to any vertex */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double ceperr; + + if (vx->ofake) + continue; + + /* Check if the vertex position is clipped by a gamut boundary. */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + pp = s->n[-1-i]; +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, pp) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + if (v > 2.0 * NUMTOL) { + /* Check whether pp is already a parent of the node */ + for (e = 0; e <= di; e++) { + if (pp->ix == vx->nix[e]) + break; + } + if (e <= di) + continue; /* It is */ + +#ifdef DEBUG + printf("Vertex %d parents %s is clipped by boundary node %d by %e\n", vx->no,pcomb(di,vx->nix),pp->ix,v); +#endif + warning("Vertex %d parents %s is clipped by boundary node %d by %e", vx->no,pcomb(di,vx->nix),pp->ix,v); + return 1; + } + } + + /* locate the nearest node to the vertex */ + if ((nn = ofps_findclosest_node(s, &ceperr, vx)) == NULL) + continue; + + /* See if it is closer than the parent nodes */ + if ((vx->eperr - ceperr) < 2.0 * NUMTOL) + continue; /* No it's not */ + + /* Check whether nn is already a parent of the node */ + for (e = 0; e <= di; e++) { + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) + continue; /* A parent */ + +#ifdef DEBUG + printf("Vertex %d is closer to %d (%f) than parent nodes %s (%f) by %e\n",vx->no,nn->ix,ceperr,pcomb(di,vx->nix),vx->eperr, ceperr - vx->eperr); +#endif + warning("Vertex %d is closer to %d (%f) than parent nodes %s (%f) by %e",vx->no,nn->ix,ceperr,pcomb(di,vx->nix),vx->eperr, ceperr - vx->eperr); + return 1; + } + fflush(stdout); + + return 0; +} + +/* ------------------------------------------------------------------- */ + +#if defined(DEBUG) || defined(DUMP_PLOT) || defined (SANITY_CHECK_CONSISTENCY) || defined(DUMP_STRUCTURE) +/* Do some sanity checking on the points */ +static void +sanity_check( + ofps *s, + int check_nodelists /* nz to check node lists */ +) { + int i, j, k, e, di = s->di; + vtx *vx1, *vx2; + int fail = 0; /* 0 = pass, 1 = soft fail, 2 = hard fail */ + +#ifdef DEBUG + printf("Running sanity check...\n"); +#endif + + /* See if any of the sample nodes are near the same location */ + for (i = 0; i < (s->np-1); i++) { + node *p1 = s->n[i]; + for (j = i+1; j < s->np; j++) { + node *p2 = s->n[j]; + double rad; + for (rad = 0.0, e = 0; e < di; e++) { + double tt = p1->p[e] - p2->p[e]; + rad += tt * tt; + } + rad = sqrt(rad); + if (rad < 1e-5) { +#ifdef DEBUG + printf("Nodes ix %d and ix %d are at %s and %s\n", i,j,ppos(di,p1->p),ppos(di,p2->p)); +#endif + fail = 2; + } + } + } + + /* See if any of the vertexes have the same node combinations */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) + vx1->sch = 0; + + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + if (vx1->sch) + continue; + for (vx2 = vx1->link; vx2 != NULL; vx2 = vx2->link) { + if (vx2->sch) + continue; + for (e = 0; e <= di; e++) { + if (vx1->nix[e] != vx2->nix[e]) + break; + } + if (e > di) { + vx1->sch = vx2->sch = 1; /* Don't do these again */ +#ifdef DEBUG + printf("Vertex ix %d and ix %d have same nix %s\n", vx1->no,vx2->no,pcomb(di,vx1->nix)); +#endif + fail = 2; + } + } + } + + /* See if any of the vertexes are at the same location */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) + vx1->sch = 0; + + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + if (vx1->sch) + continue; + for (vx2 = vx1->link; vx2 != NULL; vx2 = vx2->link) { + double rad; + if (vx2->sch) + continue; + for (rad = 0.0, e = 0; e < di; e++) { + double tt = vx1->p[e] - vx2->p[e]; + rad += tt * tt; + } + rad = sqrt(rad); + if (rad < 1e-10) { + vx1->sch = vx2->sch = 1; /* Don't do these again */ +#ifdef DEBUG + printf("Vertex no %d nix %s vm %s and no %d nix %s vm %s are at %s and %s", vx1->no,pcomb(di,vx1->nix),psm(s,&vx1->vm),vx2->no,pcomb(di,vx2->nix),psm(s,&vx2->vm),ppos(di,vx1->p),ppos(di,vx2->p)); + if (fabs(vx1->eperr - vx2->eperr) > 1e-5) + printf(" and errs %f %f\n",vx1->eperr,vx2->eperr); + else + printf("\n"); +#endif + /* See if the two vertexes are both visible to each other */ + if (sm_vtx_vtx(s, vx1, vx2) != 0) { + fail = 2; + } + } + } + } + + /* See if any of the nodes and vertexes are near the same location */ + for (i = 0; i < s->np; i++) { + node *p1 = s->n[i]; + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + double rad; + for (rad = 0.0, e = 0; e < di; e++) { + double tt = p1->p[e] - vx1->p[e]; + rad += tt * tt; + } + rad = sqrt(rad); + if (rad < 1e-5) { +#ifdef DEBUG + printf("Node ix %d and Vertex no %d are at %s and %s%s", i,vx1->no,ppos(di,p1->p),ppos(di,vx1->p),vx1->ghost ? " (ghost)" : ""); + if (vx1->eperr > 1e-5) + printf(" and err %f\n",vx1->eperr); + else + printf("\n"); +#endif + if (vx1->ghost == 0) + fail = 1; + } + } + } + + /* Check every node appears in at least one vertex */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + vtx *vx; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + for (e = 0; e <= di; e++) { + if (vx->nix[e] == p1->ix) + break; /* yes */ + } + if (e <= di) + break; /* yes */ + } + if (vx == NULL) { +#ifdef DEBUG + printf("Node ix %d has no vertexes that refer to it\n", p1->ix); +#endif + fail = 2; + } + } + + if (check_nodelists) { + /* See if any vertexes do not appear in their constituent nodes */ + /* vertex list, or whether verexes nodes don't appear in neighbour list. */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + for (e = 0; e <= di; e++) { + int ix = vx1->nix[e]; + node *pp = s->n[ix]; + + for (j = 0; j < pp->nvv; j++) { + if (pp->vv[j] == vx1) + break; + } + if (j >= pp->nvv) { +#ifdef DEBUG + printf("Vertex no %d nix %s doesn't appear in node ix %d\n", vx1->no,pcomb(di,vx1->nix),pp->ix); +#endif + fail = 2; + } + } + } + } + + /* Check that every vertex of a node contains that node. */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + for (j = 0; j < p1->nvv; j++) { /* For all its vertexes */ + vtx *vx = p1->vv[j]; + + for (e = 0; e <= di; e++) { /* All vertexes parent nodes */ + int ix = vx->nix[e]; + node *pp = s->n[ix]; + + if (ix == p1->ix) + break; + } + if (e > di) { +#ifdef DEBUG + printf("Node ix %d has vtx no %d nix %s that doesn't contain node\n", p1->ix, vx->no,pcomb(di,vx->nix)); +#endif + fail = 2; + } + } + } + + if (check_nodelists) { + /* Check that a node contains as neighbours all the parent */ + /* nodes of its vertexes */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + for (j = 0; j < p1->nvv; j++) { /* For all its vertexes */ + vtx *vx = p1->vv[j]; + + for (e = 0; e <= di; e++) { + int ix = vx->nix[e]; + node *pp = s->n[ix]; + + if (ix == p1->ix) + continue; /* Neighbours don't include self */ + for (k = 0; k < p1->nvn; k++) { + if (p1->vn[k] == ix) + break; + } + if (k >= p1->nvn) { +#ifdef DEBUG + printf("Node ix %d has vtx no %d nix %s where neighbour ix %d is missing\n", p1->ix, vx->no,pcomb(di,vx->nix),ix); +#endif + fail = 2; + } + } + } + } + } + + /* Check that the vertex net is correct */ + { + int ff, f, e, di = s->di; + vtx *vx1, *vx2; + int nnv = 0; + int _nnv = 0; + struct _vtx **nv = NULL; + + /* Do a brute force search to locate all this vertexes net neighbours */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + + nnv = 0; /* Clear the current list */ + + /* Search all other vertexes for neighbours */ + for (vx2 = s->uvtx; vx2 != NULL; vx2 = vx2->link) { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + + if (vx1 == vx2) + continue; + +#ifdef NEVER /* vertex net needs all neighbours ? */ +#ifdef INDEP_SURFACE + if (sm_vtx_vtx(s, vx1, vx2) == 0) + continue; +#endif /* INDEP_SURFACE */ +#endif + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = vx1->nix[MXPD+2]; /* nixm */ + bb = vx2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) + continue; /* It's certainly not */ + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (vx1->nix[e] == vx2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (vx1->nix[e] > vx2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) + continue; /* No match */ + + if (nnm == 0) { + error("ofps: two vertexes have the same nodes !\n" + "no %d at %s nix %s\nno %d at %s nix %s", + vx1->no,ppos(di,vx1->p),pcomb(di,vx1->nix), + vx2->no,ppos(di,vx2->p),pcomb(di,vx2->nix)); + } + if (nnv >= _nnv) { + _nnv = 2 * _nnv + 1; + if ((nv = (vtx **)realloc(nv, sizeof(vtx *) * _nnv)) == NULL) + error("ofps: realloc failed on node vertex pointers"); + } + nv[nnv++] = vx2; + } + + /* Now check that the vertex nets match */ + for (i = 0; i < nnv; i++) { + for (j = 0; j < vx1->nnv; j++) { + if (nv[i] == vx1->nv[j]) + break; + } + if (j >= vx1->nnv) { + printf("Vtx no %d is missing vtx no %d from net\n",vx1->no,nv[i]->no); + fail = 2; + } + } + for (j = 0; j < vx1->nnv; j++) { + for (i = 0; i < nnv; i++) { + if (nv[i] == vx1->nv[j]) + break; + } + if (i >= nnv) { + printf("Vtx no %d has extra vtx no %d in net\n",vx1->no,vx1->nv[j]->no); + fail = 2; + } + } + } + } + if (fail) { + if (fail == 1) + warning("Internal consistency check failed (soft)"); + else + warning("Internal consistency check failed"); +#ifdef DEBUG + if (fail == 1) + printf("Internal consistency check failed (soft)\n"); + else + printf("Internal consistency check failed\n"); + fflush(stdout); +#endif +#ifdef SANITY_CHECK_CONSISTENCY_FATAL + if (fail == 2) + error("Internal consistency check failed"); +#endif + } + +#ifdef SANITY_CHECK_EXAUSTIVE_SEARCH_FOR_VERTEXES + check_for_missing_vertexes(s); +#endif +} +#endif /* SANITY_CHECK_CONSISTENCY */ + +#if defined(DEBUG) || defined(DUMP_STRUCTURE) + +/* ------------------------------------------------------------------- */ + +/* Dump the node & vertex relationship */ +static void +dump_node_vtxs( + ofps *s, + int check_nodelists +) { + int i, j, e, di = s->di; + vtx *vx; + + printf("\n"); + printf("Dumping current state...\n"); + + /* Dump node information */ + for (i = -s->gnp; i < s->np; i++) { + node *p1 = s->n[i]; + printf("Node ix %d, pos %s, mask 0x%x, asm %s\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + } + printf("\n"); + + /* Dump vertex information */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + if (vx->ofake == 0) + printf("Vertex no %d, pmask 0x%x, cmask 0x%x, vm %s\n pos %s, nix %s, eperr = %s, eserr = %s\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm), ppos(di, vx->p), pcomb(di,vx->nix), peperr(vx->eperr), peperr(vx->eserr)); + else + printf("Vertex no %d, pmask 0x%x, cmask 0x%x, vm %s, nix %s, eperr = %s, eserr = %s (ofake)\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm), pcomb(di,vx->nix), peperr(vx->eperr), peperr(vx->eserr)); + } + printf("\n"); + + /* Dump vertex and associated vertex information */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + printf("Vertex no %d has Vtx net:",vx->no); + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + printf(" %d",vx2->no); + } + printf("\n"); + } + printf("\n"); + + /* Dump node and associated vertex information */ + for (i = -s->nbp; i < s->np; i++) { + node *p1 = s->n[i]; + printf("Node ix %d, pos %s, mask 0x%x, a_sm %s:\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + for (j = 0; j < p1->nvv; j++) { + vtx *vx = p1->vv[j]; + if (vx->ofake == 0) + printf(" Vtx no %d pmask 0x%x cmask 0x%x vm %s pos %s nix %s eserr %s\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm),ppos(di, vx->p), pcomb(di, vx->nix), peperr(vx->eserr)); + else + printf(" Vtx no %d pmask 0x%x cmask 0x%x vm %s nix %s eserr %s (ofake)\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm),pcomb(di,vx->nix), peperr(vx->eserr)); + } + for (j = 0; j < p1->nvn; j++) { + int ix = p1->vn[j]; + if (ix >= 0) { + node *n1 = s->n[ix]; + printf(" Assoc. node ix %d pos %s\n",ix,ppos(di, n1->p)); + } else { + printf(" Assoc. node ix %d\n",ix); + } + } + } + printf("\n"); + + sanity_check(s, check_nodelists); + + fflush(stdout); +} + +#ifdef NEVER +/* Special dump the node & vertex relationship, */ +/* for comparing with "good" output. */ +/* Deal with vertexe order and numbering. */ +/* Note that the "new" "bad" code needs ofake vertexes */ +/* to work, wheras the "old" "good" code doesn't, so */ +/* skip reporting ofake vetexes. */ +static void +dump_node_vtxs2( + ofps *s, + char *com +) { + int i, j, e, di = s->di; + vtx *vx; + FILE *fp; + static int cc = 0; + int showofake = 1; + vtx **vlist; + + if (cc == 0) { + if ((fp = fopen("bad.log","w")) == NULL) + error("Unable to open file '%s'\n","bad.log"); + cc = 1; + } else { + if ((fp = fopen("bad.log","a")) == NULL) + error("Unable to open file '%s'\n","bad.log"); + } + + fprintf(fp,"\n"); + fprintf(fp,"Dumping current state (%s) ...\n",com); + + /* Dump node information */ + for (i = -s->gnp; i < s->np; i++) { + node *p1 = s->n[i]; + fprintf(fp,"Node ix %d, pos %s, mask 0x%x, asm %s\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + } + fprintf(fp,"\n"); + + /* Sort the vertexes by their nix */ + { + int scl = s->gnp + s->np; + int nv; + + int nused; + for (nused = 0, vx = s->uvtx; vx != NULL; vx = vx->link) + nused++; +if (nused != s->nv) error("s->nv %d doesn't match uvtx list %d",s->nv,nused); + +//printf("~1 number of vertexes = %d\n",s->nv); + if ((vlist = (vtx **)malloc(sizeof(vtx *) * s->nv)) == NULL) + error ("ofps: malloc failed on sorted vertex list"); + for (i = nv = 0, vx = s->uvtx; vx != NULL; vx = vx->link, i++) { + if (!showofake && vx->ofake) + continue; + vlist[nv++] = vx; + + /* Convert nix into sort index */ + vx->sch = 0; + for (e = 0; e <= di; e++) { + vx->sch = scl * vx->sch + (vx->nix[e] + s->gnp); + } +//printf("~1 nix %s ix %d\n",pcomb(di,vx->nix),vx->sch); +//fflush(stdout); + } + + /* Sort */ +#define HEAP_COMPARE(A,B) ((A)->sch < (B)->sch) + HEAPSORT(vtx *, vlist, nv); +#undef HEAP_COMPARE + + for (i = 0; i < nv; i++) { + vx = vlist[i]; + vx->sch = i; /* Sorted index */ + } + + /* Dump vertex information */ + for (i = 0; i < nv; i++) { + vx = vlist[i]; + fprintf(fp,"Vertex no %d, pmask 0x%x, cmask 0x%x, vm %s\n pos %s, nix %s, eserr = %s\n",vx->sch,vx->pmask,vx->cmask,psm(s,&vx->vm), ppos(di, vx->p), pcomb(di,vx->nix), peperr(vx->eserr)); + } + fprintf(fp,"\n"); + free(vlist); + } + + /* Dump node and associated vertex information */ + for (i = -s->nbp; i < s->np; i++) { + node *p1 = s->n[i]; + vtx **vv; + int nvv; + int *vn; + + fprintf(fp,"Node ix %d, pos %s, mask 0x%x, a_sm %s\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + + /* Display the vertexes in order */ + if ((vv = (vtx **)malloc(sizeof(vtx *) * p1->nvv)) == NULL) + error ("ofps: malloc failed on sorted vertex list"); + for (nvv = j = 0; j < p1->nvv; j++) { + if (!showofake && p1->vv[j]->ofake) + continue; + vv[nvv++] = p1->vv[j]; + } +#define HEAP_COMPARE(A,B) ((A)->sch < (B)->sch) + HEAPSORT(vtx *, vv, nvv); +#undef HEAP_COMPARE + for (j = 0; j < nvv; j++) { + vtx *vx = vv[j]; + fprintf(fp," Vtx no %d pmask 0x%x cmask 0x%x vm %s pos %s nix %s eserr %s\n",vx->sch,vx->pmask,vx->cmask,psm(s,&vx->vm),ppos(di, vx->p), pcomb(di, vx->nix), peperr(vx->eserr)); + } + free(vv); + + /* Sort the nodes to be in order */ + if ((vn = (int *)malloc(sizeof(int) * p1->nvn)) == NULL) + error ("ofps: malloc failed on sorted vertex list"); + for (j = 0; j < p1->nvn; j++) + vn[j] = p1->vn[j]; +#define HEAP_COMPARE(A,B) ((A) < (B)) + HEAPSORT(int, vn, p1->nvn); +#undef HEAP_COMPARE + for (j = 0; j < p1->nvn; j++) { + int ix = vn[j]; + if (ix >= 0) { + node *n1 = s->n[ix]; + fprintf(fp," Assoc. node ix %d pos %s\n",ix,ppos(di, n1->p)); + } else { + fprintf(fp," Assoc. node ix %d\n",ix); + } + } + free(vn); + } + printf("\n"); + fflush(fp); + fclose(fp); +} +#endif /* NEVER */ +#endif /* DEBUG || DUMP_PLOT || DUMP_STRUCTURE */ + + +/* --------------------------------------------------------------- */ +#ifdef DUMP_FERR /* Create .tiff of dnsq function error */ + +/* Draw a line in the output diagnostic raster */ +static int +show_line( +ofps *s, /* ofps object */ +int x1, int y1, int x2, int y2, /* line start and end points */ +unsigned char rgb[3], /* Color */ +unsigned char *base, /* Raster base of line */ +int pitch, +int width, +int height +) { + unsigned char *pp; + int ow = width, oh = height; /* width and height of raster for clipping */ + int dx, dy; /* Line deltas */ + int adx, ady; /* Absolute deltas */ + + int e, k1, k2; /* Error and axial/diagonal error change values */ + int m1,m2; /* axial/diagonal coordinate change values */ + + int ll; /* Line length */ + + /* Do a crude clip */ + if (x1 < 0) + x1 = 0; + if (x1 >= ow) + x1 = ow-1; + if (x2 < 0) + x2 = 0; + if (x2 >= ow) + x2 = ow-1; + if (y1 < 0) + y1 = 0; + if (y1 >= oh) + y1 = oh-1; + if (y2 < 0) + y2 = 0; + if (y2 >= oh) + y2 = oh-1; + + /* calculate the standard constants */ + dx = x2 - x1; + dy = y2 - y1; + + if(dx < 0) { + m1 = -3; /* x is going backwards */ + adx = -dx; /* make this absolute */ + } else { + m1 = 3; /* x is going forwards */ + adx = dx; + } + + e = 0; + if(dy < 0) { + m2 = -pitch; /* y is going upwards (decreasing) */ + ady = -dy; /* make this absolute */ + e = -1; /* make lines retraceable */ + } else { + m2 = pitch; /* y is going downwards (increasing) */ + ady = dy; + } + + /* m1 has been set to x increment, m2 to y increment */ + + m2 += m1; /* make m2 the diagonal address increment */ + /* and m1 the x axial inrement */ + if(adx > ady) { /* x is driven */ + ll = adx; + k1 = 2 * ady; + k2 = 2 * (ady - adx); + e += k1 - adx; + } else { + ll = ady; + k1 = 2 * adx; + k2 = 2 * (adx - ady); + e += k1 - ady; + m1 = m2 - m1; /* Make m1 the y increment */ + } + + /* Start pixel of line */ + pp = base + y1 * pitch + 3 * x1; + + ll++; /* Draw start and end point */ + + while( ll > 0) { + while(e < 0 && ll > 0) { + pp[0] = rgb[0]; + pp[1] = rgb[1]; + pp[2] = rgb[2]; + pp += m1; + e += k1; + ll--; + } + while(e >= 0 && ll > 0) { + pp[0] = rgb[0]; + pp[1] = rgb[1]; + pp[2] = rgb[2]; + pp += m2; + e += k2; + ll--; + } + } + return 0; +} + +/* Dump a TIFF of the dnsq function values for a given point/plane set. */ +static void +dump_dnsqe( + ofps *s, + char *fname, + int *nix, + vopt_cx *cx +) { + int i, j, e, di = s->di; + unsigned char *base, *pa, col[2][3]; + int width = WIDTH; + int height = HEIGHT; + int pitch = width * 3; + int x, y; + TIFF *tif; + double pos[MXPD], fval[MXPD]; + double angle, mag; + + printf("Dumping dnsqe error for combination %s\n",pcomb(di,nix)); + + if ((tif = TIFFOpen(fname, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",fname); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + /* allocate a raster */ + if ((base = (unsigned char *)malloc(sizeof(unsigned char) * height * pitch)) == NULL) + error ("ofps: malloc failed on diagnostic raster"); + + for (y = 0; y < height; y++) { + pos[1] = 1.0 - y/(height-1.0); + pos[1] = 1.4 * pos[1] - 0.2; + pa = base + y * pitch; + + /* Fill in pa[] with colors for this line */ + for (x = 0; x < width; x++) { + double ss; + unsigned char *dp; + double beserr, eserr; + double bf, rgb[3]; + double oog = 1.0; + double escale = 10.0; /* Error value scaling */ + + dp = pa + x * 3; + pos[0] = x/(width-1.0); + pos[0] = 1.4 * pos[0] - 0.2; + dp[0] = dp[1] = dp[2] = 255; +//printf("~1 doing %d %d pos %f %f\n",x,y,pos[0],pos[1]); + + /* Se if the sample is in gamut */ + for (ss = 0.0, e = 0; e < s->di; e++) { + if (pos[e] < s->imin[e] + || pos[e] > s->imax[e]) + break; + ss += pos[e]; + } + if (e < s->di || ss > (s->ilimit + ILIMITEPS)) { + oog = 0.7; /* Show gamut boundary */ + } + +#ifdef NEVER /* Test colors out */ + fval[0] = pos[0] * 2.0 * escale - escale; + fval[1] = pos[1] * 2.0 * escale - escale; +#else + /* Lookup the function value here */ + dnsq_solver(cx, di, pos, fval, 0); +#endif + + /* Turn the two values into colors. */ + for (e = 0; e < di; e++) { + fval[e] = (fval[e] / escale); + if (fval[e] >= 0.0) + fval[e] = pow(fval[e], 0.5); + else + fval[e] = -pow(-fval[e], 0.5); + } + + /* Convert to angle and magnitude */ + angle = 180.0/3.1415926 * atan2(fval[0], fval[1]); + if (angle < 0.0) + angle += 360.0; + else if (angle > 360.0) + angle -= 360.0; + mag = sqrt(fval[0] * fval[0] + fval[1] * fval[1]); + if (mag > 1.0) + mag = 1.0; + + rgb[0] = rgb[1] = rgb[1] = 0.0; + if (angle < 120.0) { /* red to green */ + bf = angle / 120.0; + rgb[0] = 1.0 - bf; + rgb[1] = bf; + rgb[2] = 0.0; + } else if (angle < 240.0) { /* green to blue */ + bf = (angle - 120.0) / 120.0; + rgb[0] = 0.0; + rgb[1] = 1.0 - bf; + rgb[2] = bf; + } else { /* blue to red */ + bf = (angle - 240.0) / 120.0; + rgb[0] = bf; + rgb[1] = 0.0; + rgb[2] = 1.0 - bf; + } + + /* Scale to black with magnitude */ + for (e = 0; e < 3; e++) { + rgb[e] = 1.0 - rgb[e]; + rgb[e] = (1.0 - mag) * 0.0 + mag * rgb[e]; + } + + for (e = 0; e < 3; e++) + dp[e] = (int)(255.0 * oog * rgb[e] + 0.5); + } + } + + + /* Show the path the dnsq sampled */ + col[0][0] = col[0][1] = 255, col[0][2] = 128; + col[1][0] = 128, col[1][1] = col[1][2] = 255; + + for (i = 0; i < (cx->nl-1); i++) { +//printf("~1 line %d: %f %f -> %f %f\n",i, cx->clist[i].p[0], cx->clist[i].p[1], cx->clist[i+1].p[0], cx->clist[i+1].p[1]); + show_line(s, + (int)(((cx->clist[i].p[0] + 0.2) / 1.4) * (width - 1.0) + 0.5), + (int)((1.0 - ((cx->clist[i].p[1] + 0.2) / 1.4)) * (height - 1.0) + 0.5), + (int)(((cx->clist[i+1].p[0] + 0.2) / 1.4) * (width - 1.0) + 0.5), + (int)((1.0 - ((cx->clist[i+1].p[1] + 0.2) / 1.4)) * (height - 1.0) + 0.5), + col[i & 1], base, pitch, width, height); + } + + /* Write the raster out */ + for (y = 0; y < height; y++) { + pa = base + y * pitch; + + if (TIFFWriteScanline(tif, (tdata_t)pa, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); + free(base); +} +#endif /* DUMP_FERR */ + +/* --------------------------------------------------------------- */ + +#ifdef NEVER + + /* Compute an aproximate bounding shere, and use */ + /* the center of it as the start point. */ + + double radsq = -1.0; /* Span/radius squared */ + double rad; + double sum; + int i, j; + int bi = 0, bj = 0; + + /* Find the two vectors that are farthest apart. Brute force search */ + /* Also track the device position for the points used to define the shere */ + for (i = 0; i < (ii-1); i++) { + for (j = i+1; j < ii; j++) { + for (sum = 0.0, e = 0; e < di; e++) { + double tt = cx.nds[i]->p[e] - cx.nds[j]->p[e]; + sum += tt * tt; + } + if (sum > radsq) { + radsq = sum; + bi = i; + bj = j; + } + } + } + + /* Set initial bounding sphere */ + for (e = 0; e < di; e++) + atp[e] = 0.5 * (cx.nds[bi]->p[e] + cx.nds[bj]->p[e]); + radsq /= 4.0; /* diam^2 -> rad^2 */ + rad = sqrt(radsq); + + /* Go though all the points again, expanding sphere if necessary */ + for (i = 0; i < ii; i++) { + + if (i == bi || i == bj) + continue; + + /* Compute distance squared of vertex to bounding sphere center */ + for (sum = 0.0, e = 0; e < di; e++) { + double tt = cx.nds[i]->p[e] - atp[e]; + sum += tt * tt; + } + if (sum > radsq) { + double tt; + + sum = sqrt(sum) + 1e-10; /* Radius to point */ + rad = 0.5 * (rad + sum); + radsq = rad * rad; + tt = sum - rad; + for (e = 0; e < di; e++) + atp[e] = (rad * atp[e] + tt * cx.nds[i]->p[e])/sum; + } + } + +/* Given two sample point indexes, compute the plane between them. */ +/* (This will fail with a divide by zero error if two points are coincident) */ +static void comp_pleq(ofps *s, pleq *vp, int ix1, int ix2) { + node *p0 = s->n[ix1], *p1 = s->n[ix2]; + int e, di = s->di; + double cp[MXPD]; + double sum = 0.0; + + /* Compute plane normal from ix1 to ix2 */ + for (e = 0; e < di; e++) { + double tt = p1->p[e] - p0->p[e]; + vp->pe[e] = tt; + sum += tt * tt; + } + sum = sqrt(sum); + + /* Normalise it */ + for (e = 0; e < di; e++) + vp->pe[e] /= sum; + + /* Compute mid point */ + for (e = 0; e < di; e++) + cp[e] = 0.5 * (p1->p[e] + p0->p[e]); + + /* Compute the plane equation constant */ + for (vp->pe[di] = 0.0, e = 0; e < di; e++) + vp->pe[di] -= vp->pe[e] * cp[e]; +} + +#endif // NEVER + -- cgit v1.2.3