diff -Nru wings3d-2.2.4/BUILD.win32 wings3d-2.2.5/BUILD.win32 --- wings3d-2.2.4/BUILD.win32 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/BUILD.win32 2019-12-07 15:15:57.000000000 +0000 @@ -11,24 +11,26 @@ The following software is needed: -- The Wings source. http://www.wings3d.com +- The Wings source @ http://www.wings3d.com or git -- Msys2 (or WSL) with git and gcc for windows +- WSL or (Msys2 or WSL) for make and bash scripts -- Erlang/Otp 20.2 or later. http://www.erlang.org +- Erlang/Otp 22.1 or later. http://www.erlang.org Include the vcredist packaged with Erlang, in your installation. (It is easiest to download the pre-built binaries for Windows.) -- git or cl-1.2.3 or later. http://github.com/tonyrog/cl - Optional software ================= - NSIS 2.02 or higher, an installer/uninstaller maker. http://nsis.sf.net. -- rebar3 script see https://github.com/erlang/rebar3 +- git (to fetch sources from github) +- cl-1.2.3 or later. http://github.com/tonyrog/cl + +- rebar3 script see https://github.com/erlang/rebar3 + To build opencl package above Installing the software ======================= @@ -42,15 +44,21 @@ for Windows globally from "My Computer". Modify the PATH environment variable so that the following programs -are runnable from an Msys (bash) shell. +are runnable from an (bash) shell. - erlc.exe (Erlang/OTP) + erl.exe escript.exe erlc.exe (Erlang/OTP) +MSYS: gcc (MinGW) export GCC=x86_64-w64-mingw32-gcc (for 64bits build) -If using WSL (linux in Windows 10) - export WWSLcross=true +WSL: (Linux in Windows 10) + Currently requires Microsoft Compiler installed, + Setup the MSVC environment with: + eval `cmd.exe /C "C:/Path/to/Wings/win32/SetupWSLcross.bat" x64` + + Also if you want OpenCL built and build the installer you will need a + linux erlang installed in wsl, so that 'rebar3' and 'escript' works. To build a distribution you need: WINGS_VCREDIST needs to be set to point at vcredist.exe in your Erlang diff -Nru wings3d-2.2.4/c_src/Makefile wings3d-2.2.5/c_src/Makefile --- wings3d-2.2.4/c_src/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ wings3d-2.2.5/c_src/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -0,0 +1,93 @@ +# +# Makefile -- +# +# Makefile for building Wings "accelerator" helpers. +# +# Copyright (c) 2004-2013 Bjorn Gustavsson +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# $Id: Makefile,v 1.11 2006/01/14 09:02:38 dgud Exp $ +# +include ../erl.mk + +OS := $(shell uname -s) + +LIBDIR = ../priv + +ifeq ($(findstring CYGWIN,$(OS)),CYGWIN) + USEMINGWGCC := true + DEF_CC = mingw32-gcc +endif + +ifdef MSYSTEM + ifeq ($(MSYSTEM),$(filter $(MSYSTEM),MINGW32 MINGW64)) + USEMINGWGCC := true + DEF_CC = gcc + endif +else + ifeq ($(WSLcross), true) + UseMCL = true + endif +endif + +## UseMCL := $(shell cl.exe 2>&1 | grep Microsoft) + +ifneq (x$(UseMCL),x) + UseMCL = true + USEMINGWGCC = false + DEF_CC = cl.exe +endif + +FRD = 'io:fwrite("~n=:~s~n",[code:root_dir()]),init:stop().' +ERL_DIR := $(shell echo $(FRD) | $(ERL) | sed -n '/^=:/s/^=://p') +ERL_INC = "$(ERL_DIR)/usr/include" + +UNIVERSAL_FLAGS = +COMMON_CFLAGS = -Wall + +ifeq ($(UseMCL),true) + OUT = -Fe: + SO_EXT = dll + LIBS = -LD + CFLAGS += -nologo -Zi -Ox +else + OUT = -o + ifdef USEMINGWGCC + SO_EXT = dll + LIBS = -mdll + GL_LIBS += -lopengl32 -lglu32 + CFLAGS += $(COMMON_CFLAGS) -Werror -O3 + else + SO_EXT = so + DEF_CC = $(CC) + ifeq ($(OS),Darwin) + CFLAGS = $(UNIVERSAL_FLAGS) $(COMMON_CFLAGS) -Werror -O3 + LIBS = $(UNIVERSAL_FLAGS) -bundle -flat_namespace -undefined suppress + GL_LIBS = + else + LIBS = -shared -fpic + GL_LIBS += -L/usr/local/lib -lGLU -lGL + CFLAGS += $(COMMON_CFLAGS) -Werror -O3 + endif + endif +endif + +ifeq ($(GCC),) + GCC = $(DEF_CC) +endif + +DRV = $(LIBDIR)/wings_pick_nif.$(SO_EXT) + +TARGET_FILES=$(BEAM) $(DRV) + +all: $(TARGET_FILES) + +$(LIBDIR)/%.$(SO_EXT): %.c + install -d $(LIBDIR) + $(GCC) $(CFLAGS) -I$(ERL_INC) $(OUT) $@ $< $(LIBS) $(GL_LIBS) + +clean: + rm -f $(TARGET_FILES) vc*.pdb wings_pick_*.obj + rm -f core diff -Nru wings3d-2.2.4/c_src/wings_pick_nif.c wings3d-2.2.5/c_src/wings_pick_nif.c --- wings3d-2.2.4/c_src/wings_pick_nif.c 1970-01-01 00:00:00.000000000 +0000 +++ wings3d-2.2.5/c_src/wings_pick_nif.c 2019-12-07 15:15:57.000000000 +0000 @@ -0,0 +1,435 @@ +/* + * wings_pick_nif.c -- + * + * Erlang nif for picking. + * + * Copyright (c) 2009-2019 Bjorn Gustavsson + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + */ + +#ifdef __WIN32__ +#define WIN32_LEAN_AND_MEAN +#include +#endif +#include + +#include "erl_nif.h" + +struct vertex_struct { + float x, y, w, z; +}; +typedef struct vertex_struct vertex; +#define PRE_RES_SIZE 512 +#define MAX_RES_SIZE 1024*512 + +/* Declarations of internal functions */ +static ERL_NIF_TERM pick(float* vertices, unsigned stride, unsigned num_tris, + float* m, int, int, int, + ErlNifEnv* env); +static void mul(vertex* out, float x, float y, float z, float m[16]); +static void intersection(vertex* out, vertex* prev_vp, vertex* cur_vp, + float bc_prev, float bc_cur); +static int do_cull(vertex* vp, int ccw); +#if 0 +static void print_tri(vertex* tri); +static void print_vertex(vertex* v); +#endif + +static ERL_NIF_TERM atom_ok; +static ERL_NIF_TERM atom_true; +static ERL_NIF_TERM atom_false; + +static ERL_NIF_TERM faces(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +static ErlNifFunc nif_funcs[] = + { + {"faces_1", 6, faces}, + }; + + +static ERL_NIF_TERM faces(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary bin; + unsigned int stride; + int one_hit; + int cull; + int ccw; + int t_arity; + const ERL_NIF_TERM* tuple; + double temp; + float m[16]; + int i; + + if(!enif_get_uint(env, argv[0], &stride)) return enif_make_badarg(env); + if(!enif_inspect_binary(env, argv[1], &bin)) return enif_make_badarg(env); + one_hit = enif_is_identical(argv[2], atom_true); + cull = enif_is_identical(argv[3], atom_true); + ccw = enif_is_identical(argv[4], atom_true); + if(!enif_get_tuple(env, argv[5], &t_arity, &tuple) || t_arity != 16) + return enif_make_badarg(env); + for(i = 0; i < t_arity; i++) { + enif_get_double(env, tuple[i], &temp); + m[i] = (float) temp; + } + + return pick((float *)bin.data, stride / sizeof(float), bin.size / (stride*3), + m, ccw, cull, one_hit, env); +} + +/* + * Picking code. + */ + +static ERL_NIF_TERM +pick(float* vs, unsigned stride, unsigned num_tris, + float m[16], int ccw, int cull, int one_hit, ErlNifEnv *env) +{ + unsigned i; + + ERL_NIF_TERM res_array[PRE_RES_SIZE]; + ERL_NIF_TERM* res; + int res_size = 0; + int max = PRE_RES_SIZE; + + double last_depth = 42.0; + int nearest = -1; + + /* printf("\r\nN:stride %d n %d ccw %d cull %d one %d\r\n", + * stride, num_tris, ccw, cull, one_hit); + */ + + res = res_array; + + for (i = 0; i < num_tris; i++) { + /* Storage for triangle vertices follow. We do the clipping + * in the same buffer, moving forward all the time, never + * reusing a vertex position. Each clipping plane may add + * at most one vertex. + */ + vertex tri[3 + 4+5+6+7+8+9]; + unsigned codes[3]; /* Outcodes for all original triangle vertices */ + unsigned j; + + /* + * For clipping in the non-trivial case. + */ + vertex* prev_vp; /* Previous vertex */ + vertex* cur_vp; /* Current vertex */ + vertex* next_vp; /* Where to store the next vertex */ + int vs_left; /* Vertices left in the polygon */ + int vs_new; /* Number of new and copied vertices in + the clipped polygon */ + int plane; /* Number of current plane */ + int reject; /* Non-trivial reject boolean */ + + /* + * Pick up the three vertices in a triangle and transform them + * with the pick matrix. Calculate the outcodes for each vertex. + */ + for (j = 0; j < 3; j++) { + unsigned code; + vertex* vp = tri+j; + mul(vp, vs[0], vs[1], vs[2], m); + vs += stride; + + /* + * We set the bit for each plane if the vertex is outside + * the plane. + */ +#define OUTCODE(bit, dot) ((dot) < 0.0f ? bit : 0) + code = OUTCODE(1, vp->x); + code |= OUTCODE(2, vp->w - vp->x); + code |= OUTCODE(4, vp->y); + code |= OUTCODE(8, vp->w - vp->y); + code |= OUTCODE(16, vp->z); + code |= OUTCODE(32, vp->w - vp->z); + codes[j] = code; +#undef OUTCODE + } + + /* + * Trivial reject test. AND the out codes. If the result is + * non-zero, it means that all vertices are outside at least + * one of the planes. + */ + if (codes[0] & codes[1] & codes[2]) { +#if 0 + fprintf(stderr, "%d: Trivial reject\r\n", i); +#endif + continue; + } + + /* + * Trivial accept test. OR the out codes. If the result is + * zero, all vertices are inside all of the clipping planes. + * (Only likely to happen for marquee selections.) + */ + if ((codes[0] | codes[1] | codes[2]) == 0) { +#if 0 + fprintf(stderr, "%d: Trivial accept\r\n", i); +#endif + if(!cull || do_cull(tri, ccw)) { + if(one_hit) { + double depth = tri[0].z / tri[0].w; + if(depth < last_depth) { + last_depth = depth; + nearest = 3*i; + } + } else if(res_size < MAX_RES_SIZE) { + if(res_size == max) { + /* realloc */ + ERL_NIF_TERM *temp; + max *= 2; + temp = enif_alloc(max*sizeof(ERL_NIF_TERM)); + for(j=0; j < res_size; j++) { + temp[j] = res[j]; + } + if(res != res_array) { + enif_free(res); + } + res = temp; + } + res[res_size++] = enif_make_int(env, 3*i); + } else { + /* give up we only handle MAX_RES_SIZE objects */ + break; + } + } + continue; + } + + /* + * Start of non-trivial clipping. We must clip the polygon (which + * starts out as a triangle) against each clipping plane in turn. + * If we have less than three vertices less after clipping against + * a plane, we have a non-trivial reject. If the polygon survives + * all clipping planes we have a non-trivial accept. + */ + prev_vp = tri+2; + cur_vp = tri; + vs_left = 3; + next_vp = tri + 3; + vs_new = 0; + reject = 0; + + for (plane = 0; plane < 6; plane++) { + while (vs_left-- > 0) { + float bc_prev; /* Boundary code for previous */ + float bc_cur; /* Boundary code for current */ + + switch (plane) { + case 0: + bc_prev = prev_vp->x; + bc_cur = cur_vp->x; + break; + case 1: + bc_prev = prev_vp->w - prev_vp->x; + bc_cur = cur_vp->w - cur_vp->x; + break; + case 2: + bc_prev = prev_vp->y; + bc_cur = cur_vp->y; + break; + case 3: + bc_prev = prev_vp->w - prev_vp->y; + bc_cur = cur_vp->w - cur_vp->y; + break; + case 4: + bc_prev = prev_vp->z; + bc_cur = cur_vp->z; + break; + case 5: + bc_prev = prev_vp->w - prev_vp->z; + bc_cur = cur_vp->w - cur_vp->z; + break; + default: + abort(); + } + + if (bc_prev < 0.0f) { + /* The previous vertex is outside */ + if (bc_cur >= 0.0f) { + /* The current vertex is inside. We'll add a new vertex + * at the intersectin point on the clipping plane and + * we will keep the current vertex. + */ + intersection(next_vp, prev_vp, cur_vp, bc_prev, bc_cur); + next_vp++; + *next_vp++ = *cur_vp; + vs_new += 2; + } + /* Nothing to do if both are outside. */ + } else { + /* The previous vertex is inside. */ + if (bc_cur < 0.0f) { + /* The current vertex is outside. Add the intersection + * with the clipping plane. (Discard current vertex.) */ + intersection(next_vp, prev_vp, cur_vp, bc_prev, bc_cur); + next_vp++; + vs_new++; + } else { + /* Both vertices are inside. Keep the current vertex. */ + *next_vp++ = *cur_vp; + vs_new++; + } + } + prev_vp = cur_vp++; + } + + if (vs_new < 3) { + /* + * Too few vertices. No longer a polygon. Non-trivial reject. + */ + reject = 1; + break; + } + prev_vp = next_vp - 1; + cur_vp = next_vp - vs_new; + vs_left = vs_new; + vs_new = 0; + } +#if 0 + if (reject) { + fprintf(stderr, "%d: non-trivial reject\r\n", i); + } else { + fprintf(stderr, "%d: non-trivial reject\r\n", i); + } +#endif + if (!reject && (!cull || do_cull(cur_vp, ccw))) { + if(one_hit) { + double depth = cur_vp[0].z / cur_vp[0].w; + if(depth < last_depth) { + last_depth = depth; + nearest = 3*i; + } + } else if(res_size < MAX_RES_SIZE) { + if(res_size == max) { + /* realloc */ + ERL_NIF_TERM *temp; + max *= 2; + temp = enif_alloc(max*sizeof(ERL_NIF_TERM)); + for(j=0; j < res_size; j++) { + temp[j] = res[j]; + } + if(res != res_array) { + enif_free(res); + } + res = temp; + } + res[res_size++] = enif_make_int(env, 3*i); + } else { + /* give up we only handle MAX_RES_SIZE objects */ + break; + } + } + } + + if(res_size > 0 || nearest > -1) { + if(one_hit) { + unsigned depth; + depth = (unsigned) (last_depth * (double) (0xFFFFFFFF) + 0.5); + return enif_make_tuple2(env, + enif_make_int(env, nearest), + enif_make_uint(env, depth)); + } else { + ERL_NIF_TERM temp; + temp = enif_make_list_from_array(env, res, res_size); + if(res != res_array) enif_free(res); + return temp; + } + } else { + return enif_make_list(env, 0); + } +} + +static void +mul(vertex* out, float x, float y, float z, float m[16]) +{ + out->x = m[0]*x + m[4]*y + m[8]*z + m[12]; + out->y = m[1]*x + m[5]*y + m[9]*z + m[13]; + out->z = m[2]*x + m[6]*y + m[10]*z + m[14]; + out->w = m[3]*x + m[7]*y + m[11]*z + m[15]; +} + +static void +intersection(vertex* out, vertex* prev_vp, vertex* cur_vp, + float bc_prev, float bc_cur) +{ + float alpha = bc_prev / (bc_prev - bc_cur); + out->x = prev_vp->x + alpha * (cur_vp->x - prev_vp->x); + out->y = prev_vp->y + alpha * (cur_vp->y - prev_vp->y); + out->z = prev_vp->z + alpha * (cur_vp->z - prev_vp->z); + out->w = prev_vp->w + alpha * (cur_vp->w - prev_vp->w); +} + +static int +do_cull(vertex* vp, int ccw_is_front) +{ + /* + * Calculate the signed area of the first three vertices + * (converted to windows coordinates). + */ + float Dx02, Dx12, Dy02, Dy12; + float area; + vp[0].x /= vp[0].w; vp[0].y /= vp[0].w; + vp[1].x /= vp[1].w; vp[1].y /= vp[1].w; + vp[2].x /= vp[2].w; vp[2].y /= vp[2].w; + Dx02 = vp[0].x - vp[2].x; + Dx12 = vp[1].x - vp[2].x; + Dy02 = vp[0].y - vp[2].y; + Dy12 = vp[1].y - vp[2].y; + area = Dx02*Dy12 - Dx12*Dy02; + if (area < 0.0f && ccw_is_front) { + return 0; + } + return 1; +} + +#if 0 +static void +print_tri(vertex* tri) +{ + int j; + + for (j = 0; j < 3; j++) { + print_vertex(tri+j); + putchar(' '); + } + putchar('\r'); + putchar('\n'); +} + +static void +print_vertex(vertex* v) +{ + printf("(%f, %f, %f, %f)", v->x, v->y, v->z, v->w); +} +#endif + + + +static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + atom_ok = enif_make_atom(env,"ok"); + atom_true = enif_make_atom(env,"true"); + atom_false = enif_make_atom(env,"false"); + + // avec_r = enif_open_resource_type(env, "eblas", "avec", NULL, ERL_NIF_RT_CREATE, NULL); + return 0; +} + +static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, + ERL_NIF_TERM load_info) +{ + return 0; +} + +static void unload(ErlNifEnv* env, void* priv_data) +{ + +} + +ERL_NIF_INIT(wings_pick_nif,nif_funcs,load,NULL,upgrade,unload) diff -Nru wings3d-2.2.4/debian/changelog wings3d-2.2.5/debian/changelog --- wings3d-2.2.4/debian/changelog 2019-09-08 17:37:44.000000000 +0000 +++ wings3d-2.2.5/debian/changelog 2020-03-13 07:37:37.000000000 +0000 @@ -1,3 +1,13 @@ +wings3d (2.2.5-1) unstable; urgency=medium + + * Upload to experimental. + * New upstream development release. + * Refresh patches. + * Bump the debhelper compatibility level to 12. + * Bump the standards version to 4.5.0. + + -- Sergei Golovan Fri, 13 Mar 2020 10:37:37 +0300 + wings3d (2.2.4-4) unstable; urgency=medium * Add the GDK_BACKEND=x11 environment variable to /etc/wings3d.rc to fix diff -Nru wings3d-2.2.4/debian/compat wings3d-2.2.5/debian/compat --- wings3d-2.2.4/debian/compat 2019-09-08 17:37:44.000000000 +0000 +++ wings3d-2.2.5/debian/compat 2020-03-13 07:37:37.000000000 +0000 @@ -1 +1 @@ -10 +12 diff -Nru wings3d-2.2.4/debian/control wings3d-2.2.5/debian/control --- wings3d-2.2.4/debian/control 2019-09-08 17:37:44.000000000 +0000 +++ wings3d-2.2.5/debian/control 2020-03-13 07:37:37.000000000 +0000 @@ -3,10 +3,10 @@ Uploaders: Sergei Golovan Section: graphics Priority: optional -Build-Depends: debhelper (>= 10.0.0), grep-dctrl, +Build-Depends: debhelper (>= 12.0.0), grep-dctrl, erlang-dev (>= 1:18.0), erlang-esdl-dev (>= 1.2), erlang-cl, erlang-wx, libgl1-mesa-dev | libgl-dev, libglu1-mesa-dev | libglu-dev, libjpeg-dev -Standards-Version: 4.4.0 +Standards-Version: 4.5.0 Homepage: http://www.wings3d.com Vcs-Browser: https://salsa.debian.org/erlang-team/packages/wings3d Vcs-Git: https://salsa.debian.org/erlang-team/packages/wings3d.git diff -Nru wings3d-2.2.4/debian/patches/icons.diff wings3d-2.2.5/debian/patches/icons.diff --- wings3d-2.2.4/debian/patches/icons.diff 2019-09-08 17:37:44.000000000 +0000 +++ wings3d-2.2.5/debian/patches/icons.diff 2020-03-13 07:37:37.000000000 +0000 @@ -1,6 +1,6 @@ --- a/icons/Makefile +++ b/icons/Makefile -@@ -37,7 +37,7 @@ +@@ -38,7 +38,7 @@ # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- diff -Nru wings3d-2.2.4/debian/patches/make.diff wings3d-2.2.5/debian/patches/make.diff --- wings3d-2.2.4/debian/patches/make.diff 2019-09-08 17:37:44.000000000 +0000 +++ wings3d-2.2.5/debian/patches/make.diff 2020-03-13 07:37:37.000000000 +0000 @@ -6,17 +6,19 @@ --- a/Makefile +++ b/Makefile -@@ -25,10 +25,10 @@ +@@ -22,11 +22,11 @@ .PHONY: all debug clean lang all: $(DEPS) - (cd intl_tools; $(MAKE)) - (cd src; $(MAKE)) - (cd e3d; $(MAKE)) +- (cd c_src; $(MAKE)) - (cd plugins_src; $(MAKE)) + (cd intl_tools; $(MAKE) TYPE=opt common) + (cd src; $(MAKE) TYPE=opt common) + (cd e3d; $(MAKE) TYPE=opt common) ++ (cd c_src; $(MAKE) TYPE=opt) + (cd plugins_src; $(MAKE) TYPE=opt common) (cd icons; $(MAKE)) @cat _deps/build_log 2> /dev/null || true diff -Nru wings3d-2.2.4/debian/rules wings3d-2.2.5/debian/rules --- wings3d-2.2.4/debian/rules 2019-09-08 17:37:44.000000000 +0000 +++ wings3d-2.2.5/debian/rules 2020-03-13 07:37:37.000000000 +0000 @@ -6,7 +6,7 @@ DIR := debian/wings3d LIBDIR := /usr/lib/erlang/lib/wings-$(WINGS_VSN) LINTIANDIR := /usr/share/lintian/overrides -SUBDIRS := ebin plugins shaders textures +SUBDIRS := ebin plugins priv shaders textures %: dh $@ @@ -40,6 +40,7 @@ $(MAKE) lang override_dh_install: + find . dh_install -XREADME $(SUBDIRS) $(LIBDIR) # Remove errant executable flags to please lintian find $(DIR)$(LIBDIR) -type f -exec chmod a-x \{\} \; diff -Nru wings3d-2.2.4/.dir-locals.el wings3d-2.2.5/.dir-locals.el --- wings3d-2.2.4/.dir-locals.el 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/.dir-locals.el 2019-12-07 15:15:57.000000000 +0000 @@ -11,4 +11,4 @@ (c-basic-offset . 2)) (c-mode (indent-tabs-mode . nil) - (c-basic-offset . 4))) + (c-basic-offset . 2))) diff -Nru wings3d-2.2.4/e3d/e3d__dds.erl wings3d-2.2.5/e3d/e3d__dds.erl --- wings3d-2.2.4/e3d/e3d__dds.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/e3d/e3d__dds.erl 2019-12-07 15:15:57.000000000 +0000 @@ -221,7 +221,7 @@ Sz = b_size(W, H, Div, BSz), <> = Bin, Image = Decomp(MM,W,H), - get_mipmaps(Rest, W div 2, H div 2, Div, BSz, Level+1, Max, Decomp, + get_mipmaps(Rest, max(1,W div 2), max(1,H div 2), Div, BSz, Level+1, Max, Decomp, [{Image,W,H,Level}|Acc]). b_size(W, H, Div, BSz) -> diff -Nru wings3d-2.2.4/e3d/e3d_mat.erl wings3d-2.2.5/e3d/e3d_mat.erl --- wings3d-2.2.4/e3d/e3d_mat.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/e3d/e3d_mat.erl 2019-12-07 15:15:57.000000000 +0000 @@ -187,7 +187,7 @@ rotate_to_z(Vec) -> {Vx,Vy,Vz} = V = case e3d_vec:norm(Vec) of - {Wx,Wy,Wz}=W when abs(Wx) < abs(Wy), abs(Wx) < abs(Wz) -> + {Wx,Wy,Wz}=W when abs(Wx) =< abs(Wy), abs(Wx) < abs(Wz) -> e3d_vec:norm(0.0, Wz, -Wy); {Wx,Wy,Wz}=W when abs(Wy) < abs(Wz) -> e3d_vec:norm(Wz, 0.0, -Wx); diff -Nru wings3d-2.2.4/e3d/e3d__tri_quad.erl wings3d-2.2.5/e3d/e3d__tri_quad.erl --- wings3d-2.2.4/e3d/e3d__tri_quad.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/e3d/e3d__tri_quad.erl 2019-12-07 15:15:57.000000000 +0000 @@ -792,41 +792,51 @@ %% does segment AB intersect CD? Just touching -> false. segsintersect(IA, IB, IC, ID, Vtab) -> - A = coords2(IA,Vtab), - B = coords2(IB,Vtab), - C = coords2(IC,Vtab), - D = coords2(ID,Vtab), - U = sub2(B,A), - V = sub2(D,C), - W = sub2(A,C), - PP = perp2(U,V), - case (-1.0e-7 < PP) andalso (PP < 1.0e-7) of - false -> - SI = perp2(V,W) / PP, - TI = perp2(U,W) / PP, - (SI > 0.0 andalso SI < 1.0 - andalso TI > 0.0 andalso TI < 1.0); - true -> - %% parallel or overlapping - case {dot2(U, U),dot2(V, V)} of - {0.0,_} -> false; - {_,0.0} -> false; - {_,_} -> - %% At this point, we know that none of the - %% segments are points. - Z = sub2(B, C), - {Vx,Vy}=V, {Wx,Wy}=W, {Zx,Zy}=Z, - {T0,T1} = case Vx of - 0.0 -> - {Wy/Vy, Zy/Vy}; - _ -> - {Wx/Vx, Zx/Vx} - end, - (0.0 < T0) andalso (T0 < 1.0) andalso - (0.0 < T1) andalso (T1 < 1.0) - end - end. + {Ax,Ay} = A = coords2(IA,Vtab), + {Bx,By} = B = coords2(IB,Vtab), + {Cx,Cy} = C = coords2(IC,Vtab), + {Dx,Dy} = D = coords2(ID,Vtab), + {Ux,Uy} = U = sub2(B,A), + {Vx,Vy} = V = sub2(D,C), + {UxMin,UxMax} = if Ux < 0 -> {Bx,Ax}; true -> {Ax,Bx} end, + if Vx > 0, UxMax < Dx ; Cx < UxMin -> false; + UxMax < Cx ; Dx < UxMin -> false; + true -> + {UyMin,UyMay} = if Uy < 0 -> {By,Ay}; true -> {Ay,By} end, + if Vy > 0, UyMay < Dy ; Cy < UyMin -> false; + UyMay < Cy ; Dy < UyMin -> false; + true -> + W = sub2(A,C), + PP = perp2(U,V), + case (-1.0e-7 < PP) andalso (PP < 1.0e-7) of + false -> + SI = perp2(V,W) / PP, + TI = perp2(U,W) / PP, + (SI > 0.0 andalso SI < 1.0 + andalso TI > 0.0 andalso TI < 1.0); + true -> + %% parallel or overlapping + case {dot2(U, U),dot2(V, V)} of + {0.0,_} -> false; + {_,0.0} -> false; + {_,_} -> + %% At this point, we know that none of the + %% segments are points. + Z = sub2(B, C), + {Wx,Wy}=W, {Zx,Zy}=Z, + {T0,T1} = case Vx of + 0.0 -> {Wy/Vy, Zy/Vy}; + _ -> {Wx/Vx, Zx/Vx} + end, + (0.0 < T0) + andalso (T0 < 1.0) + andalso (0.0 < T1) + andalso (T1 < 1.0) + end + end + end + end. %% element(I,T), but wrap I if necessary to stay in range 1..N welement(I, N, T) -> element(windex(I, N), T). diff -Nru wings3d-2.2.4/e3d/Makefile wings3d-2.2.5/e3d/Makefile --- wings3d-2.2.4/e3d/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/e3d/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -11,12 +11,13 @@ # $Id$ # +include ../erl.mk + .SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ .fig .dvi .tex .class .java .pdf .psframe .pscrop ESRC=. EBIN=../ebin -ERLC=erlc ifeq ($(TYPE),debug) TYPE_FLAGS=-DDEBUG +debug_info diff -Nru wings3d-2.2.4/erl.mk wings3d-2.2.5/erl.mk --- wings3d-2.2.4/erl.mk 1970-01-01 00:00:00.000000000 +0000 +++ wings3d-2.2.5/erl.mk 2019-12-07 15:15:57.000000000 +0000 @@ -0,0 +1,9 @@ +ifeq ($(WSLcross), true) + ERL=erl.exe + ERLC=erlc.exe + ESCRIPT=escript.exe +else + ERL=erl + ERLC=erlc + ESCRIPT=escript +endif diff -Nru wings3d-2.2.4/.gitignore wings3d-2.2.5/.gitignore --- wings3d-2.2.4/.gitignore 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/.gitignore 2019-12-07 15:15:57.000000000 +0000 @@ -31,6 +31,14 @@ # /fonts/ /fonts/*.wingsfont +# /priv/ +/priv/* + +# /c_src/ +/c_src/*.o +/c_src/*.obj +/c_src/*.pdb + # /plugins/ /plugins/* @@ -40,10 +48,6 @@ # /plugins_src/autouv/ /plugins_src/autouv/autouv_en.lang -# /plugins_src/accel/ -/plugins_src/accel/*.obj -/plugins_src/accel/*.pdb - # /plugins_src/fbx/ /plugins_src/fbx/*.o /plugins_src/fbx/*.obj Binary files /tmp/tmpn2Khl7/RiYg65ZFYh/wings3d-2.2.4/icons/about_wings.bmp and /tmp/tmpn2Khl7/z1ttp1V1ss/wings3d-2.2.5/icons/about_wings.bmp differ diff -Nru wings3d-2.2.4/icons/Makefile wings3d-2.2.5/icons/Makefile --- wings3d-2.2.4/icons/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/icons/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -11,12 +11,13 @@ # $Id: Makefile,v 1.7 2006/08/23 02:56:49 antoneos Exp $ # +include ../erl.mk + .SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ .fig .dvi .tex .class .java .pdf .psframe .pscrop ESRC=. EBIN=../ebin -ERLC=erlc ICONSETS= \ classic \ @@ -50,7 +51,7 @@ mv $? $@ $(EBIN)/wings_icon_%.bundle: %/*.bmp %/*.png collect_bmp.beam about_wings.bmp - erl $(ERL_FLAGS) -noinput -run collect_bmp start . $(ESRC)/$* $@ -s erlang halt + $(ERL) $(ERL_FLAGS) -noinput -run collect_bmp start . $(ESRC)/$* $@ -s erlang halt clean: for ICONSET in $(ICONSETS); do \ diff -Nru wings3d-2.2.4/intl_tools/Makefile wings3d-2.2.5/intl_tools/Makefile --- wings3d-2.2.4/intl_tools/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/intl_tools/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -15,10 +15,10 @@ .fig .dvi .tex .class .java .pdf .psframe .pscrop include ../vsn.mk +include ../erl.mk ESRC=. EBIN=. -ERLC=erlc ifeq ($(TYPE),debug) TYPE_FLAGS=-DDEBUG +debug_info @@ -59,4 +59,4 @@ $(ERLC) $(ERL_FLAGS) $(ERL_COMPILE_FLAGS) -o$(EBIN) $< $(GEN_HEADER): gen_char_hrl $(LANG_FILES) - escript gen_char_hrl + $(ESCRIPT) gen_char_hrl diff -Nru wings3d-2.2.4/Makefile wings3d-2.2.5/Makefile --- wings3d-2.2.4/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -10,11 +10,8 @@ # # Check if OpenCL package is needed -ifeq ($(WSLcross), true) -ERL = erl.exe -else -ERL = erl -endif + +include erl.mk CL_PATH = $(shell $(ERL) -noshell -eval 'erlang:display(code:which(cl))' -s erlang halt) ifneq (,$(findstring non_existing, $(CL_PATH))) @@ -28,6 +25,7 @@ (cd intl_tools; $(MAKE)) (cd src; $(MAKE)) (cd e3d; $(MAKE)) + (cd c_src; $(MAKE)) (cd plugins_src; $(MAKE)) (cd icons; $(MAKE)) @cat _deps/build_log 2> /dev/null || true @@ -36,6 +34,7 @@ (cd intl_tools; $(MAKE) debug) (cd src; $(MAKE) debug) (cd e3d; $(MAKE) debug) + (cd c_src; $(MAKE) debug) (cd plugins_src; $(MAKE) debug) (cd icons; $(MAKE) debug) @cat _deps/build_log 2> /dev/null || true @@ -44,6 +43,7 @@ (cd intl_tools; $(MAKE) clean) (cd src; $(MAKE) clean) (cd e3d; $(MAKE) clean) + (cd c_src; $(MAKE) clean) (cd plugins_src; $(MAKE) clean) (cd icons; $(MAKE) clean) @@ -51,7 +51,7 @@ (cd intl_tools; $(MAKE)) (cd src; $(MAKE) lang) (cd plugins_src; $(MAKE) lang) - escript tools/verify_language_files . + $(ESCRIPT) tools/verify_language_files . # # Build installer for Windows. diff -Nru wings3d-2.2.4/plugins_src/accel/Makefile wings3d-2.2.5/plugins_src/accel/Makefile --- wings3d-2.2.4/plugins_src/accel/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/accel/Makefile 1970-01-01 00:00:00.000000000 +0000 @@ -1,102 +0,0 @@ -# -# Makefile -- -# -# Makefile for building Wings "accelerator" helpers. -# -# Copyright (c) 2004-2013 Bjorn Gustavsson -# -# See the file "license.terms" for information on usage and redistribution -# of this file, and for a DISCLAIMER OF ALL WARRANTIES. -# -# $Id: Makefile,v 1.11 2006/01/14 09:02:38 dgud Exp $ -# -OS := $(shell uname -s) - -ERL=erl - -ifeq ($(findstring CYGWIN,$(OS)),CYGWIN) - USEMINGWGCC := true - DEF_CC = mingw32-gcc -endif - -ifdef MSYSTEM - ifeq ($(MSYSTEM),$(filter $(MSYSTEM),MINGW32 MINGW64)) - USEMINGWGCC := true - DEF_CC = gcc - endif -else - ifeq ($(WSLcross), true) - UseMCL = true - ERL = erl.exe - endif -endif - -## UseMCL := $(shell cl.exe 2>&1 | grep Microsoft) - -ifneq (x$(UseMCL),x) - UseMCL = true - USEMINGWGCC = false - DEF_CC = cl.exe -endif - -LIBDIR = ../../plugins/accel - -FRD = 'io:fwrite("~n=:~s~n",[code:root_dir()]),init:stop().' -ERL_DIR := $(shell echo $(FRD) | $(ERL) | sed -n '/^=:/s/^=://p') -ERL_INC = "$(ERL_DIR)/usr/include" -BEAM = $(LIBDIR)/wpc_pick.beam - -UNIVERSAL_FLAGS = -COMMON_CFLAGS = -Wall - -ifeq ($(UseMCL),true) - OUT = -Fe: - SO_EXT = dll - LIBS = -LD - CFLAGS += -nologo -Zi -Ox -else - OUT = -o - ifdef USEMINGWGCC - SO_EXT = dll - LIBS = -mdll - GL_LIBS += -lopengl32 -lglu32 - CFLAGS += $(COMMON_CFLAGS) -Werror -O3 - else - SO_EXT = so - DEF_CC = $(CC) - ifeq ($(OS),Darwin) - CFLAGS = $(UNIVERSAL_FLAGS) $(COMMON_CFLAGS) -Werror -O3 - LIBS = $(UNIVERSAL_FLAGS) -bundle -flat_namespace -undefined suppress - GL_LIBS = - else - LIBS = -shared -fpic - GL_LIBS += -L/usr/local/lib -lGLU -lGL - CFLAGS += $(COMMON_CFLAGS) -Werror -O3 - endif - endif -endif - -ifeq ($(GCC),) - GCC = $(DEF_CC) -endif - -ERLC = erlc -ERLC_FLAGS = -Werror -o $(LIBDIR) +debug_info - -DRV = $(LIBDIR)/wings_pick_drv.$(SO_EXT) - -TARGET_FILES=$(BEAM) $(DRV) - -all: $(TARGET_FILES) - -$(LIBDIR)/%.beam: %.erl - install -d $(LIBDIR) - $(ERLC) $(ERLC_FLAGS) $< - -$(LIBDIR)/%.$(SO_EXT): %.c - install -d $(LIBDIR) - $(GCC) $(CFLAGS) -I$(ERL_INC) $(OUT) $@ $< $(LIBS) $(GL_LIBS) - -clean: - rm -f $(TARGET_FILES) vc*.pdb wings_pick_drv.obj - rm -f core diff -Nru wings3d-2.2.4/plugins_src/accel/wings_pick_drv.c wings3d-2.2.5/plugins_src/accel/wings_pick_drv.c --- wings3d-2.2.4/plugins_src/accel/wings_pick_drv.c 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/accel/wings_pick_drv.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,445 +0,0 @@ -/* - * wings_ogla.c -- - * - * Erlang driver for picking. - * - * Copyright (c) 2009-2010 Bjorn Gustavsson - * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. - * - * $Id: wings_ogla_drv.c,v 1.3 2004/04/20 18:14:22 bjorng Exp $ - */ - -#include -#include "erl_driver.h" - -#ifdef __WIN32__ -#define WIN32_LEAN_AND_MEAN -#include -#endif -#include - -#if (ERL_DRV_EXTENDED_MAJOR_VERSION < 2) -/* R14B or earlier types */ -#define ErlDrvSizeT int -#define ErlDrvSSizeT int -#endif - -/* - * Interface routines. - */ -static ErlDrvData wings_file_start(ErlDrvPort port, char *buff); -static void wings_file_stop(ErlDrvData handle); -static ErlDrvSSizeT control(ErlDrvData handle, unsigned int command, - char* buff, ErlDrvSizeT count, - char** res, ErlDrvSizeT res_size); -static void outputv(ErlDrvData drv_data, ErlIOVec* ev); - -/* - * The driver struct - */ -ErlDrvEntry wings_file_driver_entry = { - NULL, /* F_PTR init, N/A */ - wings_file_start, /* L_PTR start, called when port is opened */ - wings_file_stop, /* F_PTR stop, called when port is closed */ - NULL, /* F_PTR output, called when erlang has sent */ - NULL, /* F_PTR ready_input, called when input descriptor - ready */ - NULL, /* F_PTR ready_output, called when output - descriptor ready */ - "wings_pick_drv", /* char *driver_name, the argument to open_port */ - NULL, /* F_PTR finish, called when unloaded */ - NULL, /* void * that is not used (BC) */ - control, /* F_PTR control, port_control callback */ - NULL, /* F_PTR timeout, driver_set_timer callback */ - outputv, /* F_PTR outputv, reserved */ - NULL, /* async */ - NULL, /* flush */ - NULL, /* call */ - NULL, /* Event */ - ERL_DRV_EXTENDED_MARKER, - ERL_DRV_EXTENDED_MAJOR_VERSION, - ERL_DRV_EXTENDED_MINOR_VERSION, - ERL_DRV_FLAG_USE_PORT_LOCKING, /* Port lock */ - NULL, /* Reserved Handle */ - NULL, /* Process Exited */ -}; - -struct vertex_struct { - float x, y, w, z; -}; -typedef struct vertex_struct vertex; - -/* - * Driver data (non-reetrant, non-thread safe). - */ -#define MAX_RES_SIZE (1024*1204) -static float m[16]; -static ErlDrvBinary* result; -static unsigned res_size; -static int cull = 1; -static int ccw_is_front = 1; -static int one_hit = 1; -static double last_depth; - -/* Declarations of internal functions */ -static void pick(float* vertices, unsigned stride, unsigned num_tris); -static void mul(vertex* out, float x, float y, float z); -static void intersection(vertex* out, vertex* prev_vp, vertex* cur_vp, - float bc_prev, float bc_cur); -static void do_accept(unsigned i, vertex* vp); -#if 0 -static void print_tri(vertex* tri); -static void print_vertex(vertex* v); -#endif - -/* - * Driver initialization routine - */ -DRIVER_INIT(wings_file_drv) -{ - return &wings_file_driver_entry; -} - -/* - * Open a port - */ -static ErlDrvData wings_file_start(ErlDrvPort port, char *buff) -{ - result = driver_alloc_binary(MAX_RES_SIZE); - return (ErlDrvData) port; -} - -/* - * Close a port - */ -static void wings_file_stop(ErlDrvData handle) -{ - /* Nothing to do yet. */ -} - -/* - * Handle commands. - */ - -static ErlDrvSSizeT -control(ErlDrvData handle, unsigned int command, - char* buf, ErlDrvSizeT count, - char** res, ErlDrvSizeT res_size) -{ - switch (command) { - case 0: { /* Define matrix */ - memcpy((void *) m, (void *) buf, count); -#if 0 - { - int i, j; - for (i = 0; i < 4; i++) { - for (j = 0; j < 4; j++) { - printf("%f ", m[i][j]); - } - printf("\r\n"); - } - printf("\r\n\r\n"); - } -#endif - return 0; - } - case 1: { /* Enable/disable culling */ - cull = buf[0]; - return 0; - } - case 2: { /* Define front-facing faces */ - ccw_is_front = buf[0]; - return 0; - } - case 3: { - one_hit = buf[0]; /* One hit (if non-zero) or all hits */ - return 0; - } - default: - return -1; - } -} - -static void -outputv(ErlDrvData drv_data, ErlIOVec* ev) -{ - ErlDrvPort port = (ErlDrvPort) drv_data; - - if (ev->vsize == 3 && ev->iov[1].iov_len == 4) { - unsigned stride = *(unsigned *) ev->iov[1].iov_base; - float* base = (float *) ev->iov[2].iov_base; - unsigned n = ev->iov[2].iov_len / stride / 3; - - pick(base, stride / sizeof(float), n); - driver_output_binary(port, 0, 0, result, 0, res_size); - } -} - -/* - * Picking code. - */ - -static void -pick(float* vs, unsigned stride, unsigned num_tris) -{ - unsigned i; - - res_size = 0; - last_depth = 42.0; - for (i = 0; i < num_tris; i++) { - /* Storage for triangle vertices follow. We do the clipping - * in the same buffer, moving forward all the time, never - * reusing a vertex position. Each clipping plane may add - * at most one vertex. - */ - vertex tri[3 + 4+5+6+7+8+9]; - unsigned codes[3]; /* Outcodes for all original triangle vertices */ - unsigned j; - - /* - * For clipping in the non-trivial case. - */ - vertex* prev_vp; /* Previous vertex */ - vertex* cur_vp; /* Current vertex */ - vertex* next_vp; /* Where to store the next vertex */ - int vs_left; /* Vertices left in the polygon */ - int vs_new; /* Number of new and copied vertices in - the clipped polygon */ - int plane; /* Number of current plane */ - int reject; /* Non-trivial reject boolean */ - - /* - * Pick up the three vertices in a triangle and transform them - * with the pick matrix. Calculate the outcodes for each vertex. - */ - for (j = 0; j < 3; j++) { - unsigned code; - vertex* vp = tri+j; - mul(vp, vs[0], vs[1], vs[2]); - vs += stride; - - /* - * We set the bit for each plane if the vertex is outside - * the plane. - */ -#define OUTCODE(bit, dot) ((dot) < 0.0f ? bit : 0) - code = OUTCODE(1, vp->x); - code |= OUTCODE(2, vp->w - vp->x); - code |= OUTCODE(4, vp->y); - code |= OUTCODE(8, vp->w - vp->y); - code |= OUTCODE(16, vp->z); - code |= OUTCODE(32, vp->w - vp->z); - codes[j] = code; -#undef OUTCODE - } - - /* - * Trivial reject test. AND the out codes. If the result is - * non-zero, it means that all vertices are outside at least - * one of the planes. - */ - if (codes[0] & codes[1] & codes[2]) { -#if 0 - fprintf(stderr, "%d: Trivial reject\r\n", i); -#endif - continue; - } - - /* - * Trivial accept test. OR the out codes. If the result is - * zero, all vertices are inside all of the clipping planes. - * (Only likely to happen for marquee selections.) - */ - if ((codes[0] | codes[1] | codes[2]) == 0) { -#if 0 - fprintf(stderr, "%d: Trivial accept\r\n", i); -#endif - do_accept(3*i, tri); - continue; - } - - /* - * Start of non-trivial clipping. We must clip the polygon (which - * starts out as a triangle) against each clipping plane in turn. - * If we have less than three vertices less after clipping against - * a plane, we have a non-trivial reject. If the polygon survives - * all clipping planes we have a non-trivial accept. - */ - prev_vp = tri+2; - cur_vp = tri; - vs_left = 3; - next_vp = tri + 3; - vs_new = 0; - reject = 0; - - for (plane = 0; plane < 6; plane++) { - while (vs_left-- > 0) { - float bc_prev; /* Boundary code for previous */ - float bc_cur; /* Boundary code for current */ - - switch (plane) { - case 0: - bc_prev = prev_vp->x; - bc_cur = cur_vp->x; - break; - case 1: - bc_prev = prev_vp->w - prev_vp->x; - bc_cur = cur_vp->w - cur_vp->x; - break; - case 2: - bc_prev = prev_vp->y; - bc_cur = cur_vp->y; - break; - case 3: - bc_prev = prev_vp->w - prev_vp->y; - bc_cur = cur_vp->w - cur_vp->y; - break; - case 4: - bc_prev = prev_vp->z; - bc_cur = cur_vp->z; - break; - case 5: - bc_prev = prev_vp->w - prev_vp->z; - bc_cur = cur_vp->w - cur_vp->z; - break; - default: - abort(); - } - - if (bc_prev < 0.0f) { - /* The previous vertex is outside */ - if (bc_cur >= 0.0f) { - /* The current vertex is inside. We'll add a new vertex - * at the intersectin point on the clipping plane and - * we will keep the current vertex. - */ - intersection(next_vp, prev_vp, cur_vp, bc_prev, bc_cur); - next_vp++; - *next_vp++ = *cur_vp; - vs_new += 2; - } - /* Nothing to do if both are outside. */ - } else { - /* The previous vertex is inside. */ - if (bc_cur < 0.0f) { - /* The current vertex is outside. Add the intersection - * with the clipping plane. (Discard current vertex.) */ - intersection(next_vp, prev_vp, cur_vp, bc_prev, bc_cur); - next_vp++; - vs_new++; - } else { - /* Both vertices are inside. Keep the current vertex. */ - *next_vp++ = *cur_vp; - vs_new++; - } - } - prev_vp = cur_vp++; - } - - if (vs_new < 3) { - /* - * Too few vertices. No longer a polygon. Non-trivial reject. - */ - reject = 1; - break; - } - prev_vp = next_vp - 1; - cur_vp = next_vp - vs_new; - vs_left = vs_new; - vs_new = 0; - } -#if 0 - if (reject) { - fprintf(stderr, "%d: non-trivial reject\r\n", i); - } else { - fprintf(stderr, "%d: non-trivial reject\r\n", i); - } -#endif - if (!reject) { - do_accept(3*i, cur_vp); - } - } -} - -static void -mul(vertex* out, float x, float y, float z) -{ - out->x = m[0]*x + m[4]*y + m[8]*z + m[12]; - out->y = m[1]*x + m[5]*y + m[9]*z + m[13]; - out->z = m[2]*x + m[6]*y + m[10]*z + m[14]; - out->w = m[3]*x + m[7]*y + m[11]*z + m[15]; -} - -static void -intersection(vertex* out, vertex* prev_vp, vertex* cur_vp, - float bc_prev, float bc_cur) -{ - float alpha = bc_prev / (bc_prev - bc_cur); - out->x = prev_vp->x + alpha * (cur_vp->x - prev_vp->x); - out->y = prev_vp->y + alpha * (cur_vp->y - prev_vp->y); - out->z = prev_vp->z + alpha * (cur_vp->z - prev_vp->z); - out->w = prev_vp->w + alpha * (cur_vp->w - prev_vp->w); -} - -static void -do_accept(unsigned i, vertex* vp) -{ - if (cull) { - /* - * Calculate the signed area of the first three vertices - * (converted to windows coordinates). - */ - float Dx02, Dx12, Dy02, Dy12; - float area; - vp[0].x /= vp[0].w; vp[0].y /= vp[0].w; - vp[1].x /= vp[1].w; vp[1].y /= vp[1].w; - vp[2].x /= vp[2].w; vp[2].y /= vp[2].w; - Dx02 = vp[0].x - vp[2].x; - Dx12 = vp[1].x - vp[2].x; - Dy02 = vp[0].y - vp[2].y; - Dy12 = vp[1].y - vp[2].y; - area = Dx02*Dy12 - Dx12*Dy02; - if (area < 0.0f && ccw_is_front) { - return; - } - } - - if (one_hit) { - double depth = vp[0].z / vp[0].w; - if (depth < last_depth) { - unsigned *p = (unsigned *) result->orig_bytes; - *p++ = i; - *p++ = (unsigned) (depth * (double) (0xFFFFFFFF) + 0.5); - res_size = 2*sizeof(unsigned); - last_depth = depth; - } - } else if (res_size < MAX_RES_SIZE) { - unsigned *p = (unsigned *) (result->orig_bytes+res_size); - *p = i; - res_size += sizeof(unsigned); - } -} - -#if 0 -static void -print_tri(vertex* tri) -{ - int i, j; - - for (j = 0; j < 3; j++) { - print_vertex(tri+j); - putchar(' '); - } - putchar('\r'); - putchar('\n'); -} - -static void -print_vertex(vertex* v) -{ - printf("(%f, %f, %f, %f)", v->x, v->y, v->z, v->w); -} -#endif - diff -Nru wings3d-2.2.4/plugins_src/accel/wpc_pick.erl wings3d-2.2.5/plugins_src/accel/wpc_pick.erl --- wings3d-2.2.4/plugins_src/accel/wpc_pick.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/accel/wpc_pick.erl 1970-01-01 00:00:00.000000000 +0000 @@ -1,457 +0,0 @@ -%% -%% wpc_pick.erl -- -%% -%% This module handles picking using our own driver. -%% -%% Copyright (c) 2009-2011 Bjorn Gustavsson -%% -%% See the file "license.terms" for information on usage and redistribution -%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. -%% -%% $Id$ -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% -%% This module and driver implements picking of faces, edges, and vertices -%% without using the selection support in OpenGL. The selection support in -%% OpenGL is not use by many applications, so it may not work reliably in -%% all implementations of OpenGL. -%% -%% Conceptually, we use the same algorithm as OpenGL. We set up a special -%% view volume that only includes a few pixels on each side of the mouse -%% cursor (or what is inside the marquee) using a special pick matrix. -%% We then go through all objects in the scene to which faces/edges/vertices -%% fall inside the view volume. -%% -%% We use a different pick matrix and view volume than OpenGL, namely -%% 0 <= X <= 1, 0 <= Y <= 1, 0 <= Z <= 1 instead of -1 <= X <= 1, -%% -1 <= Y <= 1, -1 <= Z <= 1 as suggested by Jim Blinn in -%% A Trip Down the Graphics Pipeline. -%% -%% Main references: -%% -%% Jim Blinn: A Trip Down the Graphics Pipeline. Chapter 13: Line Clipping. -%% -%% Paul Heckbert: Generic Convex Polygon Scan Conversion and Clipping in -%% Graphics Gems. -%% --module(wpc_pick). --export([init/0,pick_matrix/5,matrix/2,cull/1,culling/0,front_face/1, - faces/2,vertices/1,edges/1]). - -%% Comment out the following line to use the pure Erlang -%% reference implementation. --define(USE_DRIVER, 1). - --import(lists, [foldl/3,last/1,sort/1]). --define(FL, 32/native-float). - -init() -> - case get(wings_not_running) of - undefined -> - Dir = filename:dirname(code:which(?MODULE)), - Name = "wings_pick_drv", - case erl_ddll:load_driver(Dir, Name) of - ok -> ok; - {error,Reason} -> - io:format("Failed to load ~s in ~s\n~s\n", - [Name,Dir,erl_ddll:format_error(Reason)]), - erlang:halt() - end, - try - Port = open_port({spawn_driver,Name}, [binary]), - register(wings_pick_port, Port) - catch error:_ -> - io:format("Failed to open port ~s.\n", [Name]), - erlang:halt() - end; - _ -> - ignore - end, - false. - -%% pick_matrix(X, Y, Xs, Ys, ViewPort) -> PickMatrix -%% Set up a pick matrix like glu:pickMatrix/5, -%% but with a diffrent viewing volume (the cube (0...1)^3 -%% instead of (-1...1)^3). -%% -pick_matrix(X, Y, Xs, Ys, ViewPort) -> - M0 = e3d_transform:translate(e3d_transform:identity(), {0.5, 0.5, 0.5}), - M1 = e3d_transform:scale(M0, {0.5,0.5,0.5}), - Pick = e3d_transform:pick(X, Y, Xs, Ys, ViewPort), - e3d_transform:mul(M1, Pick). - -%% matrix(ModelViewMatrix, ProjectionMatrix) -%% Set the matrix to use for picking by combining the model -%% view and projection matrices. -%% -matrix(Model, Proj) when is_list(Model) -> - matrix(list_to_tuple(Model), Proj); -matrix(Model, Proj) when is_list(Proj) -> - matrix(Model, list_to_tuple(Proj)); -matrix(Model, Proj) -> - Mat = e3d_mat:mul(Proj, Model), - case put({?MODULE,matrix}, Mat) of - Mat -> ok; - _ -> drv_matrix(Mat) - end. - -%% cull(true|false) -%% Enable or disable backface culling when picking. -cull(false) -> - case erase({?MODULE,cull}) of - undefined -> ok; - _ -> drv_cull(0) - end; -cull(true) -> - case put({?MODULE,cull}, true) of - true -> ok; - _ -> drv_cull(1) - end. - -culling() -> - get({?MODULE,cull}) =:= true. - -%% front_face(ccw|cw) -%% Define the vertex order for front facing triangles. -front_face(ccw) -> - case erase({?MODULE,front_face}) of - undefined -> ok; - _ -> drv_ccw_is_front(1) - end; -front_face(cw) -> - case put({?MODULE,front_face}, cw) of - cw -> ok; - _ -> drv_ccw_is_front(0) - end. - -%% faces({Stride,VertexBuffer}, OneHit) -> {Index,Depth} | [Index] -%% Depth = 0..2^32-1 (0 means the near clipping plane) -%% Given a vertex buffer containing triangles, either return -%% {Index,Depth} (if OneHit is 'true'), or a list of indices for each -%% triangle that are wholly or partly inside the viewing volume -%% (if OneHit is 'false'). -%% --ifdef(USE_DRIVER). -faces({_,<<>>}, _) -> - %% An empty binary is most probably not reference-counted, - %% so we must *not* send it down to the driver. (The length - %% of the I/O vector will be 2, not 3, and the driver will - %% ignore the request without sending any data back to us.) - []; -faces({Stride,Bin}, OneHit0) -> - OneHit = case OneHit0 of - false -> <<0>>; - true -> <<1>> - end, - erlang:port_control(wings_pick_port, 3, OneHit), - erlang:port_command(wings_pick_port, [<>,Bin]), - receive - {Port,{data,Data}} when is_port(Port) -> - case OneHit0 of - false -> - [Hit || <> <= Data]; - true -> - case Data of - <<>> -> - []; - <> -> - {Hit,Depth} - end - end - end. --else. -faces({Stride,Bin}, OneHit) -> - Matrix = get({?MODULE,matrix}), - Cull0 = get({?MODULE,cull}) =:= true, - CwIsFront = get({?MODULE,front_face}) =:= cw, - Cull = case {Cull0,CwIsFront} of - {false,_} -> none; - {true,true} -> cull_ccw; - {true,false} -> cull_cw - end, - faces_1(Bin, Stride-12, Matrix, Cull, OneHit, 0, []). --endif. - -%% vertices([{Vertex,Position}]) -> [Vertex] -%% Return a list of all vertices that are inside the -%% viewing volume. -%% -vertices(VsPos) -> - Matrix = get({?MODULE,matrix}), - vertices_1(VsPos, Matrix, []). - -%% vertices([{Edge,Position}]) -> [Edges] -%% Return a list of all edges that are inside the -%% viewing volume. -%% -edges(EsPos) -> - Matrix = get({?MODULE,matrix}), - edges_1(EsPos, Matrix, []). - -%%% -%%% Communication with the driver. -%%% - --ifndef(USE_DRIVER). -%% Dummies when there is no driver. -drv_matrix(_) -> ok. -drv_cull(_) -> ok. -drv_ccw_is_front(_) -> ok. --else. -drv_matrix(Mat0) -> - Mat = << <> || F <- tuple_to_list(Mat0) >>, - drv(0, Mat). - -drv_cull(Bool) -> drv(1, [Bool]). - -drv_ccw_is_front(Bool) -> drv(2, [Bool]). - -drv(Cmd, Data) -> - erlang:port_control(wings_pick_port, Cmd, Data). --endif. - -%%% -%%% Internal functions. -%%% - -vertices_1([{V,{X0,Y0,Z0}}|T], Mat, Acc) -> - {X,Y,Z,W} = e3d_mat:mul(Mat, {X0,Y0,Z0,1.0}), - Inside = X >= 0 andalso W-X >= 0 andalso - Y >= 0 andalso W-Y >= 0 andalso - Z >= 0 andalso W-Z >= 0, - case Inside of - true -> - vertices_1(T, Mat, [V|Acc]); - false -> - vertices_1(T, Mat, Acc) - end; -vertices_1([], _, Acc) -> Acc. - -edges_1([{E,{P0,P1}}|T], Mat, Acc) -> - case edge_visible(P0, P1, Mat) of - false -> edges_1(T, Mat, Acc); - true -> edges_1(T, Mat, [E|Acc]) - end; -edges_1([], _, Acc) -> - Acc. - -edge_visible({X0,Y0,Z0}, {X1,Y1,Z1}, Mat) -> - Pos0 = e3d_mat:mul(Mat, {X0,Y0,Z0,1.0}), - Pos1 = e3d_mat:mul(Mat, {X1,Y1,Z1,1.0}), - OutCode0 = outcode(Pos0), - OutCode1 = outcode(Pos1), - case OutCode0 band OutCode1 of - 0 -> - case OutCode0 bor OutCode1 of - 0 -> - %% Both endpoints are inside all planes. Trivial accept. - true; - Clip -> - non_trivial_edge_visible(OutCode0, Clip, Pos0, Pos1, - 32, 0.0, 1.0) - end; - _ -> - %% Both endpoints are outside the same plane. Trivial reject. - false - end. - -non_trivial_edge_visible(_Code0, _Clip, _P0, _P1, 0, _A0, _B0) -> - %% The line has been clipped against all planes and - %% rejected. Non-trivial accept. - true; -non_trivial_edge_visible(Code0, Clip, P0, P1, Plane, A0, B0) -> - case Clip band Plane of - 0 -> - %% Both endpoints are on the inside side of the plane. - %% Continue with the next plane. - non_trivial_edge_visible(Code0, Clip, P0, P1, Plane bsr 1, A0, B0); - _ -> - %% One endpoint outside, one inside. - Dot0 = pdot2(Plane, P0), - Dot1 = pdot2(Plane, P1), - NewAlpha = Dot0 / (Dot0 - Dot1), - {A,B} = case Code0 band Plane of - 0 -> - {A0,min(B0, NewAlpha)}; - _ -> - {max(A0, NewAlpha),B0} - end, - if - B < A -> - %% The line is completely outside of all planes. - %% Non-trivial reject. - false; - true -> - non_trivial_edge_visible(Code0, Clip, P0, P1, Plane bsr 1, A, B) - end - end. - --define(SHIFT_OUT(Code, Dot), if Dot < 0.0 -> (Code bsl 1) bor 1; - true -> Code bsl 1 end). -outcode({X,_,_,_}=P) -> - outcode_1(P, ?SHIFT_OUT(0, X)). - -outcode_1({X,_,_,W}=P, Code) -> - outcode_2(P, ?SHIFT_OUT(Code, W-X)). - -outcode_2({_,Y,_,_}=P, Code) -> - outcode_3(P, ?SHIFT_OUT(Code, Y)). - -outcode_3({_,Y,_,W}=P, Code) -> - outcode_4(P, ?SHIFT_OUT(Code, W-Y)). - -outcode_4({_,_,Z,_}=P, Code) -> - outcode_5(P, ?SHIFT_OUT(Code, Z)). - -outcode_5({_,_,Z,W}, Code) -> - ?SHIFT_OUT(Code, W-Z). - -pdot2(32, {X,_,_,_}) -> X; -pdot2(16, {X,_,_,W}) -> W-X; -pdot2(8, {_,Y,_,_}) -> Y; -pdot2(4, {_,Y,_,W}) -> W-Y; -pdot2(2, {_,_,Z,_}) -> Z; -pdot2(1, {_,_,Z,W}) -> W-Z. - - -%%% -%%% Reference implementation in pure Erlang of face picking. -%%% - --ifndef(USE_DRIVER). -faces_1(Bin0, Unused, Mat, Cull, OneHit, I, Acc) -> - case Bin0 of - <> -> - Tri0 = [{X1,Y1,Z1,1.0},{X2,Y2,Z2,1.0},{X3,Y3,Z3,1.0}], - Tri = [e3d_mat:mul(Mat, P) || P <- Tri0], - case clip_tri(Tri, Cull) of - [] -> - %% Outside. - faces_1(Bin, Unused, Mat, Cull, OneHit, I+3, Acc); - [{_,_,Z,W}|_] -> - %% Inside. Now clipped to the view frustum. - Depth = round((Z/W)*16#FFFFFFFF), - faces_1(Bin, Unused, Mat, Cull, OneHit, I+3, [{Depth,I}|Acc]) - end; - <<>> -> - Hits = sort(Acc), - case OneHit of - false -> - sort([Hit || {_,Hit} <- Hits]); - true -> - case Hits of - [] -> []; - [{Depth,Hit}|_] -> {Hit,Depth} - end - end - end. - -clip_tri(Tri, Cull) -> - case clip_tri_1(Tri) of - [] -> []; - Vs -> - Inside = case Cull of - none -> true; - cull_cw -> is_ccw(Vs); - cull_ccw -> not is_ccw(Vs) - end, - case Inside of - true -> Vs; - false -> [] - end - end. - -clip_tri_1(Tri) -> - OutCodes = [outcode(P) || P <- Tri], - And = foldl(fun(Code, Acc) -> Code band Acc end, 16#3F, OutCodes), - case And of - 0 -> - Or = foldl(fun(Code, Acc) -> Code bor Acc end, 0, OutCodes), - case Or of - 0 -> - %% All vertices are inside all planes. Trivial accept. - Tri; - _ -> - %% Handle the non-trivial cases. - non_trivial(Tri) - end; - _ -> - %% All vertices are outside at least one of the planes. - %% Trivial reject. - [] - end. - -non_trivial(Ps) -> - non_trivial_1(Ps, 6). - -non_trivial_1(Ps, 0) -> - %% Checked against all planes and not rejected. Non-trivial accept. - Ps; -non_trivial_1(Ps0, Plane) -> - case non_trivial_2(last(Ps0), Ps0, Plane) of - Ps when length(Ps) < 3 -> - %% No longer a triangle or polygon. Non-trivial reject. - []; - Ps -> - non_trivial_1(Ps, Plane-1) - end. - -non_trivial_2(Prev, [P|T], Plane) -> - case {out(Plane, Prev),out(Plane, P)} of - {false,false} -> - %% Both inside. Keep current vertex (P). - [P|non_trivial_2(P, T, Plane)]; - {false,true} -> - %% Previous inside, current outside. - %% Keep intersection of edge and clipping plane. - [intersection(Prev, P, Plane)|non_trivial_2(P, T, Plane)]; - {true,false} -> - %% Previous outside, current inside. - %% Keep intersection of edge and clipping plane, - %% followed by current. - [intersection(Prev, P, Plane),P|non_trivial_2(P, T, Plane)]; - {true,true} -> - %% Both outside. Remove the current vertex. - non_trivial_2(P, T, Plane) - end; -non_trivial_2(_, [], _) -> []. - -is_ccw([{X1,Y1,_,W1},{X2,Y2,_,W2},{X3,Y3,_,W3}|_]) -> - XWinC = X3/W3, - YWinC = Y3/W3, - DxAC = X1/W1 - XWinC, - DxBC = X2/W2 - XWinC, - DyAC = Y1/W1 - YWinC, - DyBC = Y2/W2 - YWinC, - Area = DxAC * DyBC - DxBC * DyAC, - Area >= 0.0. - -intersection(P0, P1, Plane) -> - Dot0 = pdot(Plane, P0), - Dot1 = pdot(Plane, P1), - A = Dot0 / (Dot0 - Dot1), - add(P0, mul(sub(P1, P0), A)). - -mul({V10,V11,V12,V13}, S) when is_float(S) -> - {V10*S,V11*S,V12*S,V13}. - -sub({V10,V11,V12,V13}, {V20,V21,V22,V23}) -> - {V10-V20,V11-V21,V12-V22,V13-V23}. - -add({V10,V11,V12,V13}, {V20,V21,V22,V23}) - when is_float(V10), is_float(V11), is_float(V12) -> - {V10+V20,V11+V21,V12+V22,V13+V23}. - -out(Plane, P) -> pdot(Plane, P) < 0.0. - -pdot(1, {X,_,_,_}) -> X; -pdot(2, {X,_,_,W}) -> W-X; -pdot(3, {_,Y,_,_}) -> Y; -pdot(4, {_,Y,_,W}) -> W-Y; -pdot(5, {_,_,Z,_}) -> Z; -pdot(6, {_,_,Z,W}) -> W-Z. --endif. diff -Nru wings3d-2.2.4/plugins_src/autouv/auv_texture.erl wings3d-2.2.5/plugins_src/autouv/auv_texture.erl --- wings3d-2.2.4/plugins_src/autouv/auv_texture.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/autouv/auv_texture.erl 2019-12-07 15:15:57.000000000 +0000 @@ -315,6 +315,7 @@ gl:pushAttrib(?GL_ALL_ATTRIB_BITS), Current = wings_wm:viewport(), + Scale = wings_wm:win_scale(), UsingFbo = setup_fbo(TexW,TexH), {W0,H0} = if not UsingFbo -> wings_wm:top_size(); @@ -325,7 +326,7 @@ {W,Wd} = calc_texsize(W0, TexW), {H,Hd} = calc_texsize(H0, TexH), %% io:format("Get texture sz ~p ~p ~n", [{W,Wd},{H,Hd}]), - set_viewport({0,0,W,H}), + set_viewport({0,0,W,H}, 1), Geom = make_vbo(Geom0, Reqs), try Do = fun(Pass) -> @@ -354,7 +355,7 @@ #sh_conf{fbo_d=DeleteMe} -> DeleteMe() end, wings_vbo:delete(Geom#ts.vbo), - set_viewport(Current), + set_viewport(Current, Scale), gl:readBuffer(?GL_BACK), gl:popAttrib(), gl:blendEquationSeparate(?GL_FUNC_ADD, ?GL_FUNC_ADD), @@ -529,9 +530,9 @@ Str = lists:flatten([Str0|SzStr]), gen_tx_sizes(Sz div 2, [{Str,Sz}|Acc]). -set_viewport({X,Y,W,H}=Viewport) -> +set_viewport({X,Y,W,H}=Viewport, Scale) -> put(wm_viewport, Viewport), - gl:viewport(X, Y, W, H). + gl:viewport(X, Y, round(W*Scale), round(H*Scale)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff -Nru wings3d-2.2.4/plugins_src/autouv/Makefile wings3d-2.2.5/plugins_src/autouv/Makefile --- wings3d-2.2.4/plugins_src/autouv/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/autouv/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -11,13 +11,14 @@ # $Id: Makefile,v 1.16 2006/01/17 23:22:01 dgud Exp $ # +include ../../erl.mk + .SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ .fig .dvi .tex .class .java .pdf .psframe .pscrop ESRC=. WINGS_INTL=../../intl_tools EBIN=../../plugins/autouv -ERLC=erlc WINGS_TOP=../../.. ifeq ($(TYPE),debug) @@ -58,11 +59,11 @@ (cd shaders; $(MAKE)) template: opt - erl -pa $(WINGS_INTL) -noinput -run tools generate_template $(EBIN) -run erlang halt + $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template $(EBIN) -run erlang halt lang: template cp *.lang $(EBIN) - erl -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) clean: (cd shaders; $(MAKE) clean) diff -Nru wings3d-2.2.4/plugins_src/autouv/wpc_autouv.erl wings3d-2.2.5/plugins_src/autouv/wpc_autouv.erl --- wings3d-2.2.4/plugins_src/autouv/wpc_autouv.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/autouv/wpc_autouv.erl 2019-12-07 15:15:57.000000000 +0000 @@ -361,25 +361,13 @@ [ImId] -> ImId; _ -> wings_image:new("auvBG",bg_image()) end. - -change_texture_id(NewId, MatName, GeomSt0=#st{mat=Materials}) -> - case gb_trees:lookup(MatName, Materials) of - none -> - GeomSt0; - {value,Mat0} -> - Maps0 = proplists:get_value(maps, Mat0, []), - Maps = lists:keyreplace(diffuse,1,Maps0,{diffuse,NewId}), - Mat = lists:keyreplace(maps,1,Mat0,{maps,Maps}), - GeomSt0#st{mat=gb_trees:update(MatName,Mat,Materials)} - end. - %%%% Menus. command_menu(body, X, Y) -> Menu = [{?__(2,"Move"), {move, move_directions(false)}, ?__(3,"Move selected charts")}, - {?__(4,"Scale"), {scale, scale_directions(false) ++ + {?__(4,"Scale"), {scale, scale_directions(false) ++ [separator] ++ stretch_directions() ++ [separator, {?__(411,"Normalize Sizes"), normalize, @@ -622,7 +610,7 @@ end, command_menu(Mode, X, Y) end; -handle_event_3({drop,_,DropData}, St) -> +handle_event_3({drop,DropData}, St) -> handle_drop(DropData, St); handle_event_3({action,{{auv,_},create_texture}},_St) -> auv_texture:draw_options(); @@ -640,17 +628,17 @@ TexName = case get_texture(MatName0, St) of false -> atom_to_list(MatName0); Old -> - OldE3d = wings_image:info(Old), + OldE3d = wings_image:info(Old), case OldE3d#e3d_image.name of "auvBG" -> atom_to_list(MatName0); Other -> Other end end, - {GeomSt,MatName} = - update_texture(Tx#e3d_image{name=TexName}, - MatName0, GeomSt0), + {GeomSt,MatName} = update_texture(Tx#e3d_image{name=TexName}, + MatName0, GeomSt0), + ImId = get_texture(MatName, GeomSt), wings_wm:send(geom, {new_state,GeomSt}), - get_event(St#st{bb=Uvs#uvstate{st=GeomSt,matname=MatName}}) + get_event(St#st{bb=Uvs#uvstate{bg_img=ImId, st=GeomSt,matname=MatName}}) end; %% Others handle_event_3({vec_command,Command,_St}, _) when is_function(Command, 0) -> @@ -1435,19 +1423,11 @@ {yes,?__(1,"Drop: Change the texture image")}; drag_filter(_) -> no. -handle_drop({image,Id,_Im}, #st{bb=Uvs0}=St) -> - #uvstate{st=GeomSt0,matname=MatName} = Uvs0, - case MatName of - none -> - Uvs = Uvs0#uvstate{bg_img=Id}, - get_event(St#st{bb=Uvs}); - _ -> - GeomSt = change_texture_id(Id,MatName,GeomSt0), - wings_wm:send(geom, {new_state,GeomSt}), - Uvs = Uvs0#uvstate{st=GeomSt,matname=MatName}, - get_event(St#st{bb=Uvs}) - end; +handle_drop(#{type:=image,id:=Id}, #st{bb=Uvs0}=St) -> + Uvs = Uvs0#uvstate{bg_img=Id}, + get_event(St#st{bb=Uvs}); handle_drop(_DropData, _) -> + ?dbg("Ignore ~P~n",[_DropData,30]), keep. %% is_power_of_two(X) -> @@ -1467,13 +1447,25 @@ end. update_geom_state(#st{mat=Mat,shapes=Shs}=GeomSt, AuvSt0) -> - case new_geom_state_1(Shs, AuvSt0#st{mat=Mat}) of + case new_geom_state_0(Shs, Mat, AuvSt0) of {AuvSt1,ForceRefresh0} -> {AuvSt,ForceRefresh1} = update_selection(GeomSt, AuvSt1), {AuvSt,ForceRefresh0 or ForceRefresh1}; Other -> Other %% delete end. +new_geom_state_0(Shs, Mat, #st{bb=#uvstate{matname=none}}=AuvSt) -> + new_geom_state_1(Shs, AuvSt#st{mat=Mat}); +new_geom_state_0(Shs, Mtab0, #st{bb=#uvstate{matname=MatName}=BB, mat=Mtab1}=AuvSt) -> + case {get_texture(MatName, Mtab0), get_texture(MatName, Mtab1)} of + {Same,Same} -> + new_geom_state_1(Shs, AuvSt#st{mat=Mtab0}); + {New, _} when New =/= false -> + new_geom_state_1(Shs, AuvSt#st{mat=Mtab0, bb=BB#uvstate{bg_img=New}}); + _ -> + new_geom_state_1(Shs, AuvSt#st{mat=Mtab0}) + end. + new_geom_state_1(Shs, #st{bb=#uvstate{id=Id,st=#st{shapes=Orig}}}=AuvSt) -> case {gb_trees:lookup(Id, Shs),gb_trees:lookup(Id, Orig)} of {none,_} -> delete; @@ -1908,7 +1900,7 @@ %%% Draw routines. %%% -draw_background(#st{bb=#uvstate{matname=MatName,st=St,bg_img=Image}}) -> +draw_background(#st{bb=#uvstate{bg_img=Image}}) -> gl:pushAttrib(?GL_ALL_ATTRIB_BITS), wings_view:load_matrices(false), @@ -1931,11 +1923,7 @@ case get({?MODULE,show_background}) of false -> ok; _ -> - Tx = case get_texture(MatName,St) of - false -> wings_image:txid(Image); - DiffId -> wings_image:txid(DiffId) - end, - case Tx of + case wings_image:txid(Image) of none -> ignore; %% Avoid crash if TexImage is deleted Tx -> gl:enable(?GL_TEXTURE_2D), diff -Nru wings3d-2.2.4/plugins_src/autouv/wpc_snap_win.erl wings3d-2.2.5/plugins_src/autouv/wpc_snap_win.erl --- wings3d-2.2.4/plugins_src/autouv/wpc_snap_win.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/autouv/wpc_snap_win.erl 2019-12-07 15:15:57.000000000 +0000 @@ -524,7 +524,7 @@ init([Frame, Name, {OpaVal,Tiled}=State]) -> #{bg:=BG, text:=_FG} = Cols = wings_frame:get_colors(), - Panel = wxPanel:new(Frame, [{style, ?wxNO_BORDER}, {size,{200,300}}]), + Panel = wxPanel:new(Frame, [{style, wings_frame:get_border()}, {size,{200,300}}]), wxPanel:setFont(Panel, ?GET(system_font_wx)), wxWindow:setBackgroundColour(Panel, BG), Main = wxBoxSizer:new(?wxVERTICAL), @@ -536,13 +536,17 @@ setup_image_list(ImgLst), BAct = wxToggleButton:new(Panel, ?wxID_ANY, snap_label(activate)), wxWindow:setToolTip(BAct, wxToolTip:new(snap_tooltip(activate))), - CtrlBox = wxPanel:new(Panel, [{style, ?wxNO_BORDER}]), + CtrlBox = wxPanel:new(Panel), wxWindow:setBackgroundColour(CtrlBox, BG), %% layout settings for the first level controls Szr1 = wxBoxSizer:new(?wxVERTICAL), - wxSizer:add(Szr1, ImgLst, [{flag, ?wxEXPAND}]), - wxSizer:add(Szr1, BAct, [{flag, ?wxEXPAND}]), - wxSizer:add(Szr1, CtrlBox, [{proportion, 1}, {border, 2}, {flag, ?wxEXPAND}]), + ExpandBorder = [{border, 3}, {flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}], + wxSizer:addSpacer(Szr1, 3), + wxSizer:add(Szr1, ImgLst, [{border, 7}, {flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}]), + wxSizer:addSpacer(Szr1, 3), + wxSizer:add(Szr1, BAct, [{border, 6}, {flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}]), + wxSizer:addSpacer(Szr1, 3), + wxSizer:add(Szr1, CtrlBox, [{proportion, 1}|ExpandBorder]), %% second level (Panel content): Snap Image, Opacity, Rotation BSnap = wxButton:new(CtrlBox, ?wxID_ANY, [{label,snap_label(snap)}]), @@ -556,20 +560,22 @@ RotLbl = wxStaticText:new(CtrlBox, ?wxID_ANY, append_value(rotate,0.0)), Rot = wxSlider:new(CtrlBox, ?wxID_ANY, 0, -360, 360), wxWindow:setToolTip(Rot, wxToolTip:new(snap_tooltip(rotate))), - ModLbx = wxListBox:new(CtrlBox, ?wxID_ANY, [{style, ?wxLB_SINGLE}, {choices, mod_choices()}]), + ModLbx = wxListBox:new(CtrlBox, ?wxID_ANY, + [{style, ?wxLB_SINGLE bor ?wxBORDER_NONE}, + {choices, mod_choices()}]), wxWindow:setBackgroundColour(ModLbx, BG), %% layout settings for the second level controls Szr2 = wxBoxSizer:new(?wxVERTICAL), - wxSizer:add(Szr2, BSnap, [{proportion, 0}, {border, 2}, {flag, ?wxEXPAND}]), + wxSizer:add(Szr2, BSnap, [{proportion, 0}|ExpandBorder]), wxSizer:addSpacer(Szr2, 3), - wxSizer:add(Szr2, TileChk, [{proportion, 0}, {border, 2}, {flag, ?wxEXPAND}]), + wxSizer:add(Szr2, TileChk, [{proportion, 0}|ExpandBorder]), wxSizer:addSpacer(Szr2, 3), - wxSizer:add(Szr2, OpaLbl, [{proportion, 0}, {border, 2}, {flag, ?wxEXPAND}]), - wxSizer:add(Szr2, Opa, [{proportion, 0}, {border, 2}, {flag, ?wxEXPAND}]), - wxSizer:add(Szr2, RotLbl, [{proportion, 0}, {border, 2}, {flag, ?wxEXPAND}]), - wxSizer:add(Szr2, Rot, [{proportion, 0}, {border, 2}, {flag, ?wxEXPAND}]), + wxSizer:add(Szr2, OpaLbl, [{proportion, 0}|ExpandBorder]), + wxSizer:add(Szr2, Opa, [{proportion, 0}|ExpandBorder]), + wxSizer:add(Szr2, RotLbl, [{proportion, 0}|ExpandBorder]), + wxSizer:add(Szr2, Rot, [{proportion, 0}|ExpandBorder]), wxSizer:addSpacer(Szr2, 3), - wxSizer:add(Szr2, ModLbx, [{proportion, 1}, {border, 2}, {flag, ?wxEXPAND}]), + wxSizer:add(Szr2, ModLbx, [{proportion, 1}|ExpandBorder]), wxPanel:setSizer(CtrlBox, Szr2), wxSizer:add(Main, Szr1, [{proportion, 1}, {flag, ?wxEXPAND}]), diff -Nru wings3d-2.2.4/plugins_src/commands/Makefile wings3d-2.2.5/plugins_src/commands/Makefile --- wings3d-2.2.4/plugins_src/commands/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/commands/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -10,6 +10,7 @@ # # $Id: Makefile,v 1.21 2006/06/29 19:57:58 giniu Exp $ # +include ../../erl.mk .SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ .fig .dvi .tex .class .java .pdf .psframe .pscrop @@ -17,7 +18,6 @@ ESRC=. WINGS_INTL=../../intl_tools EBIN=../../plugins/commands -ERLC=erlc WINGS_TOP=../../.. WINGS_E3D=../../e3d @@ -78,11 +78,11 @@ $(MAKE) TYPE=$@ common template: opt - erl -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) lang: template cp *.lang $(EBIN) - erl -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) common: $(TARGET_FILES) diff -Nru wings3d-2.2.4/plugins_src/commands/wpc_sel_win.erl wings3d-2.2.5/plugins_src/commands/wpc_sel_win.erl --- wings3d-2.2.4/plugins_src/commands/wpc_sel_win.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/commands/wpc_sel_win.erl 2019-12-07 15:15:57.000000000 +0000 @@ -175,7 +175,7 @@ Panel = wxPanel:new(Frame), wxPanel:setFont(Panel, ?GET(system_font_wx)), Szr = wxBoxSizer:new(?wxVERTICAL), - Style = ?wxLC_REPORT bor ?wxLC_NO_HEADER bor ?wxLC_EDIT_LABELS bor ?wxLC_SINGLE_SEL, + Style = ?wxLC_REPORT bor ?wxLC_NO_HEADER bor ?wxLC_EDIT_LABELS bor ?wxLC_SINGLE_SEL bor wings_frame:get_border(), LC = wxListCtrl:new(Panel, [{style, Style}]), wxListCtrl:setBackgroundColour(LC, BG), wxListCtrl:setForegroundColour(LC, FG), diff -Nru wings3d-2.2.4/plugins_src/commands/wpc_views_win.erl wings3d-2.2.5/plugins_src/commands/wpc_views_win.erl --- wings3d-2.2.4/plugins_src/commands/wpc_views_win.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/commands/wpc_views_win.erl 2019-12-07 15:15:57.000000000 +0000 @@ -150,7 +150,8 @@ Panel = wxPanel:new(Frame), wxPanel:setFont(Panel, ?GET(system_font_wx)), Szr = wxBoxSizer:new(?wxVERTICAL), - Style = ?wxLC_REPORT bor ?wxLC_NO_HEADER bor ?wxLC_EDIT_LABELS bor ?wxLC_SINGLE_SEL, + Style = ?wxLC_REPORT bor ?wxLC_NO_HEADER bor ?wxLC_EDIT_LABELS bor + ?wxLC_SINGLE_SEL bor wings_frame:get_border(), LC = wxListCtrl:new(Panel, [{style, Style}]), wxListCtrl:setBackgroundColour(LC, BG), wxListCtrl:setForegroundColour(LC, FG), diff -Nru wings3d-2.2.4/plugins_src/import_export/Makefile wings3d-2.2.5/plugins_src/import_export/Makefile --- wings3d-2.2.4/plugins_src/import_export/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/import_export/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -11,13 +11,14 @@ # $Id: Makefile,v 1.25 2006/09/06 22:52:18 antoneos Exp $ # +include ../../erl.mk + .SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ .fig .dvi .tex .class .java .pdf .psframe .pscrop ESRC=. WINGS_INTL=../../intl_tools EBIN=../../plugins/import_export -ERLC=erlc WINGS_E3D=../../e3d WINGS_TOP=../../.. @@ -35,6 +36,7 @@ collada_import \ wpc_gltf \ wpc_hlines \ + wpc_jscad \ wpc_kerky \ wpc_lwo \ wpc_obj \ @@ -64,11 +66,11 @@ $(MAKE) TYPE=$@ common template: opt - erl -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) lang: template cp *.lang $(EBIN) - erl -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) common: $(TARGET_FILES) diff -Nru wings3d-2.2.4/plugins_src/import_export/wpc_ai.erl wings3d-2.2.5/plugins_src/import_export/wpc_ai.erl --- wings3d-2.2.4/plugins_src/import_export/wpc_ai.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/import_export/wpc_ai.erl 2019-12-07 15:15:57.000000000 +0000 @@ -7,7 +7,6 @@ %% - Only Illustrator version 8 or less files parsed (v9 -> pdf) %% - Ignore line width, fill, clip mask, text %% -%% To work, the wpc_tt plugin must also be loaded. %% %% $Id$ %% @@ -15,6 +14,9 @@ -module(wpc_ai). -export([init/0,menu/2,command/2,tryimport/2]). % tryimport is for debugging +%% exported to wpc_ps wpc_svg_path +-export([findpolyareas/1, polyareas_to_faces/1, subdivide_pas/2]). + -import(lists, [reverse/1,splitwith/2,member/2, sublist/2,nthtail/2,map/2]). @@ -23,8 +25,10 @@ -define(SCALEFAC, 0.01). % amount to scale AI coords by --record(cedge,% polyarea and cedge records must match definitions in wpc_tt.erl - {vs,cp1=nil,cp2=nil,ve}). %all are {x,y} pairs +-record(cedge, {vs,cp1=nil,cp2=nil,ve}). %all are {x,y} pairs +-record(polyarea, + {boundary, %%list of cedges (CCW oriented, closed) + islands=[]}). %%list of lists of cedges (CW, closed) -record(path, {ops=[], %list of pathops @@ -78,12 +82,12 @@ Objs = tokenize_bin(Rest), Closedpaths = [ P || P <- Objs, P#path.close == true ], Cntrs = getcontours(Closedpaths), - Pas = wpc_tt:findpolyareas(Cntrs), - Pas1 = wpc_tt:subdivide_pas(Pas,Nsubsteps), - {Vs0,Fs,HEs} = wpc_tt:polyareas_to_faces(Pas1), + Pas = findpolyareas(Cntrs), + Pas1 = subdivide_pas(Pas,Nsubsteps), + {Vs0,Fs,HEs} = polyareas_to_faces(Pas1), Center = e3d_vec:average(e3d_vec:bounding_box(Vs0)), Vec = e3d_vec:sub(e3d_vec:zero(),Center), - Vs = lists:reverse(center_object(Vec,Vs0)), + Vs = reverse(center_object(Vec,Vs0)), Efs = [ #e3d_face{vs=X} || X <- Fs], Mesh = #e3d_mesh{type=polygon,vs=Vs,fs=Efs,he=HEs}, Obj = #e3d_object{name=Name,obj=Mesh}, @@ -288,3 +292,318 @@ #cedge{vs={Xs*F,Ys*F},cp1=nil,cp2=nil,ve={Xe*F,Ye*F}}; scalece(#cedge{vs={Xs,Ys},cp1={X1,Y1},cp2={X2,Y2},ve={Xe,Ye}},F) -> #cedge{vs={Xs*F,Ys*F},cp1={X1*F,Y1*F},cp2={X2*F,Y2*F},ve={Xe*F,Ye*F}}. + + +%% Copied from old wpc_tt.erl +%% + +polyareas_to_faces(Pas) -> + VFpairs = map(fun pa2object/1, Pas), + concatvfs(VFpairs). + +concatvfs(Vfp) -> concatvfs(Vfp, 0, [], [], []). + +concatvfs([{Vs,Fs,HardEdges}|Rest], Offset, Vsacc, Fsacc, Hdacc) -> + Fs1 = offsetfaces(Fs, Offset), + He1 = offsetfaces(HardEdges, Offset), + Off1 = Offset + length(Vs), + concatvfs(Rest, Off1, [Vs|Vsacc], Fsacc ++ Fs1, He1 ++ Hdacc); +concatvfs([], _Offset, Vsacc, Fsacc, Hdacc) -> + He = build_hard_edges(Hdacc, []), + {lists:flatten(reverse(Vsacc)),Fsacc, He}. + +build_hard_edges([[First|_]=Loop|Rest], All) -> + New = build_hard_edges(Loop, First, All), + build_hard_edges(Rest, New); +build_hard_edges([], All) -> All. + +build_hard_edges([A|[B|_]=Rest], First, All) -> + build_hard_edges(Rest, First, [{A,B}|All]); +build_hard_edges([Last], First, All) -> + [{Last, First}|All]. + +%% Subdivide (bisect each each) Nsubsteps times. +%% When bezier edges are subdivided, the inserted point goes +%% at the proper place on the curve. +subdivide_pas(Pas,0) -> Pas; +subdivide_pas(Pas,Nsubsteps) -> + map(fun (Pa) -> subdivide_pa(Pa,Nsubsteps) end, Pas). + +subdivide_pa(Pa, 0) -> + Pa; +subdivide_pa(#polyarea{boundary=B,islands=Isls}, N) -> + subdivide_pa(#polyarea{boundary=subdivide_contour(B), + islands=map(fun subdivide_contour/1, Isls)}, N-1). + +subdivide_contour(Cntr) -> + lists:flatten(map(fun (CE) -> subdivide_cedge(CE,0.5) end, Cntr)). + +%% subdivide CE at parameter Alpha, returning two new CE's in list. +subdivide_cedge(#cedge{vs=Vs,cp1=nil,cp2=nil,ve=Ve},Alpha) -> + Vm = lininterp(Alpha, Vs, Ve), + [#cedge{vs=Vs,ve=Vm}, #cedge{vs=Vm,ve=Ve}]; +subdivide_cedge(#cedge{vs=Vs,cp1=C1,cp2=C2,ve=Ve},Alpha) -> + B0 = {Vs,C1,C2,Ve}, + B1 = bezstep(B0,1,Alpha), + B2 = bezstep(B1,2,Alpha), + B3 = bezstep(B2,3,Alpha), + [#cedge{vs=element(1,B0),cp1=element(1,B1),cp2=element(1,B2),ve=element(1,B3)}, + #cedge{vs=element(1,B3),cp1=element(2,B2),cp2=element(3,B1),ve=element(4,B0)}]. + +bezstep(B,R,Alpha) -> + list_to_tuple(bzss(B,0,3-R,Alpha)). + +bzss(_B,I,Ilim,_Alpha) when I > Ilim -> []; +bzss(B,I,Ilim,Alpha) -> + [lininterp(Alpha,element(I+1,B),element(I+2,B)) | bzss(B,I+1,Ilim,Alpha)]. + +lininterp(F,{X1,Y1},{X2,Y2}) -> {(1.0-F)*X1 + F*X2, (1.0-F)*Y1 + F*Y2}. + +findpolyareas(Cconts) -> + Areas = map(fun ccarea/1, Cconts), + {Cc,_Ar} = orientccw(Cconts, Areas), + Cct = list_to_tuple(Cc), + N = size(Cct), + Art = list_to_tuple(Areas), + Lent = list_to_tuple(map(fun length/1,Cc)), + Seqn = lists:seq(1,N), + Cls = [ {{I,J},classifyverts(element(I,Cct),element(J,Cct))} + || I <- Seqn, J <- Seqn], + Clsd = gb_trees:from_orddict(Cls), + Cont = [ {{I,J},contains(I,J,Art,Lent,Clsd)} + || I <- Seqn, J <- Seqn], + Contd = gb_trees:from_orddict(Cont), + Assigned = gb_sets:empty(), + getpas(1,N,Contd,Cct,{[],Assigned}). + +getpas(I,N,Contd,Cct,{Pas,Ass}) when I > N -> + case length(gb_sets:to_list(Ass)) of + N -> + reverse(Pas); + _ -> + %% not all assigned: loop again + getpas(1,N,Contd,Cct,{Pas,Ass}) + end; +getpas(I,N,Contd,Cct,{Pas,Ass}=Acc) -> + case gb_sets:is_member(I,Ass) of + true -> getpas(I+1,N,Contd,Cct,Acc); + _ -> + case isboundary(I,N,Contd,Ass) of + true -> + %% have a new polyarea with boundary = contour I + Ass1 = gb_sets:add(I,Ass), + {Isls,Ass2} = getisls(I,N,N,Contd,Ass1,Ass1,[]), + Cisls = lists:map(fun (K) -> revccont(element(K,Cct)) end, Isls), + Pa = #polyarea{boundary=element(I,Cct), islands=Cisls}, + getpas(I+1,N,Contd,Cct,{[Pa|Pas],Ass2}); + _ -> getpas(I+1,N,Contd,Cct,Acc) + end + end. + +%% Return true if thre is no unassigned J <= second arg, J /= I, +%% such that contour J contains contour I. +isboundary(_I,0,_Contd,_Ass) -> true; +isboundary(I,I,Contd,Ass) -> isboundary(I,I-1,Contd,Ass); +isboundary(I,J,Contd,Ass) -> + case gb_sets:is_member(J,Ass) of + true -> + isboundary(I,J-1,Contd,Ass); + _ -> + case gb_trees:get({J,I},Contd) of + true -> false; + _ -> isboundary(I,J-1,Contd,Ass) + end + end. + +%% Find islands for contour I : i.e., unassigned contours directly inside it. +%% Only have to check J and less. +%% Ass, Isls are (assigned-so-far, islands-so-far). +%% Ass0 is assigned before we started adding islands. +%% Return {list of island indices, Assigned array with those indices added} +getisls(_I,0,_N,_Contd,_Ass0,Ass,Isls) -> {reverse(Isls),Ass}; +getisls(I,J,N,Contd,Ass0,Ass,Isls) -> + case gb_sets:is_member(J,Ass) of + true -> + getisls(I,J-1,N,Contd,Ass0,Ass,Isls); + _ -> + case directlycont(I,J,N,Contd,Ass0) of + true -> + getisls(I,J-1,N,Contd,Ass0,gb_sets:add(J,Ass),[J|Isls]); + _ -> + getisls(I,J-1,N,Contd,Ass0,Ass,Isls) + end + end. + +directlycont(I,J,N,Contd,Ass) -> + gb_trees:get({I,J},Contd) andalso + lists:foldl(fun (K,DC) -> + DC andalso + (K == J orelse gb_sets:is_member(K,Ass) orelse + not(gb_trees:get({K,J},Contd))) end, + true, lists:seq(1,N)). + +ccarea(Ccont) -> + 0.5 * lists:foldl(fun (#cedge{vs={X1,Y1},ve={X2,Y2}},A) -> + A + X1*Y2 - X2*Y1 end, + 0.0, Ccont). + +%% Reverse contours if area is negative (meaning they were Clockwise), +%% and return revised Cconts and Areas. +orientccw(Cconts, Areas) -> orientccw(Cconts, Areas, [], []). + +orientccw([], [], Cacc, Aacc) -> + { reverse(Cacc), reverse(Aacc) }; +orientccw([C|Ct], [A|At], Cacc, Aacc) -> + if + A >= 0.0 -> + orientccw(Ct, At, [C|Cacc], [A|Aacc]); + true -> + orientccw(Ct, At, [revccont(C)|Cacc], [-A|Aacc]) + end. + +revccont(C) -> reverse(map(fun revcedge/1, C)). + +%% reverse a cedge +revcedge(#cedge{vs=Vs,cp1=Cp1,cp2=Cp2,ve=Ve}) -> + #cedge{vs=Ve,cp1=Cp2,cp2=Cp1,ve=Vs}. + +%% classify vertices of contour B with respect to contour A. +%% return {# inside A, # on A}. +classifyverts(A,B) -> lists:foldl(fun (#cedge{vs=Vb},Acc) -> cfv(A,Vb,Acc) end, + {0,0}, B). + + +%% Decide whether vertex P is inside or on (as a vertex) contour A, +%% and return modified pair. Assumes A is CCW oriented. +%% CF Eric Haines ptinpoly.c in Graphics Gems IV +cfv(A,P,{Inside,On}) -> + #cedge{vs=Va0} = lists:last(A), + if + Va0 == P -> + {Inside, On+1}; + true -> + Yflag0 = (element(2,Va0) > element(2,P)), + case vinside(A, Va0, P, false, Yflag0) of + true -> {Inside+1, On}; + false -> {Inside, On}; + on -> {Inside, On+1} + end + end. + +vinside([], _V0, _P, Inside, _Yflag0) -> + Inside; +vinside([#cedge{vs={X1,Y1}=V1}|Arest], {X0,Y0}, P={Xp,Yp}, Inside, Yflag0) -> + if + V1 == P -> + on; + true -> + Yflag1 = (Y1 > Yp), + Inside1 = + if + Yflag0 == Yflag1 -> Inside; + true -> + Xflag0 = (X0 >= Xp), + Xflag1 = (X1 >= Xp), + if + Xflag0 == Xflag1 -> + case Xflag0 of + true -> not(Inside); + _ -> Inside + end; + true -> + Z = X1 - (Y1-Yp)*(X0-X1)/(Y0-Y1), + if + Z >= Xp -> not(Inside); + true -> Inside + end + end + end, + vinside(Arest, V1, P, Inside1, Yflag1) + end. + +%% I, J are indices into tuple Cct of curved contours. +%% Clsd is gb_tree mapping {I,J} to [Inside,On,Outside]. +%% Return true if contour I contains at least 55% of contour J's vertices. +%% (This low percentage is partly because we are dealing with polygonal approximations +%% to curves, sometimes, and the containment relation may seem worse than it actually is.) +%% Lengths (in Lent tuple) are used for calculating percentages. +%% Areas (in Art tuple) are used for tie-breaking. +%% Return false if contour I is different from contour J, and not contained in it. +%% Return same if I == J or all vertices on I are on J (duplicate contour). +contains(I,I,_,_,_) -> + same; +contains(I,J,Art,Lent,Clsd) -> + LenI = element(I,Lent), + LenJ = element(J,Lent), + {JinsideI,On} = gb_trees:get({I,J},Clsd), + if + JinsideI == 0 -> + false; + On == LenJ, LenI == LenJ -> + same; + true -> + if + float(JinsideI) / float(LenJ) > 0.55 -> + {IinsideJ,_} = gb_trees:get({J,I},Clsd), + FIinJ = float(IinsideJ) / float(LenI), + if + FIinJ > 0.55 -> + element(I,Art) >= element(J,Art); + true -> + true + end; + true -> + false + end + end. + +%% Return {Vs,Fs} where Vs is list of {X,Y,Z} for vertices 0, 1, ... +%% and Fs is list of lists, each sublist is a face (CCW ordering of +%% (zero-based) indices into Vs). +pa2object(#polyarea{boundary=B,islands=Isls}) -> + Vslist = [cel2vec(B, 0.0) | map(fun (L) -> cel2vec(L, 0.0) end, Isls)], + Vtop = lists:flatten(Vslist), + Vbot = lists:map(fun ({X,Y,Z}) -> {X,Y,Z-0.2} end, Vtop), + Vs = Vtop ++ Vbot, + Nlist = [length(B) | map(fun (L) -> length(L) end, Isls)], + Ntot = lists:sum(Nlist), + Fs1 = [FBtop | Holestop] = faces(Nlist,0,top), + Fs2 = [FBbot | Holesbot] = faces(Nlist,Ntot,bot), + Fsides = sidefaces(Nlist, Ntot), + FtopQ = e3d__tri_quad:quadrangulate_face_with_holes(FBtop, Holestop, Vs), + FbotQ = e3d__tri_quad:quadrangulate_face_with_holes(FBbot, Holesbot, Vs), + Ft = [ F#e3d_face.vs || F <- FtopQ ], + Fb = [ F#e3d_face.vs || F <- FbotQ ], + Fs = Ft ++ Fb ++ Fsides, + {Vs,Fs, [ F#e3d_face.vs || F <- Fs1 ++ Fs2]}. + +cel2vec(Cel, Z) -> map(fun (#cedge{vs={X,Y}}) -> {X,Y,Z} end, Cel). + +faces(Nlist,Org,Kind) -> faces(Nlist,Org,Kind,[]). + +faces([],_Org,_Kind,Acc) -> reverse(Acc); +faces([N|T],Org,Kind,Acc) -> + FI = case Kind of + top -> #e3d_face{vs=lists:seq(Org, Org+N-1)}; + bot -> #e3d_face{vs=lists:seq(Org+N-1, Org, -1)} + end, + faces(T,Org+N,Kind,[FI|Acc]). + +sidefaces(Nlist,Ntot) -> sidefaces(Nlist,0,Ntot,[]). + +sidefaces([],_Org,_Ntot,Acc) -> lists:append(reverse(Acc)); +sidefaces([N|T],Org,Ntot,Acc) -> + End = Org+N-1, + Fs = [ [I, Ntot+I, wrap(Ntot+I+1,Ntot+Org,Ntot+End), wrap(I+1,Org,End)] + || I <- lists:seq(Org, End) ], + sidefaces(T,Org+N,Ntot,[Fs|Acc]). + +%% I should be in range (Start, Start+1, ..., End). Make it so. +wrap(I,Start,End) -> Start + ((I-Start) rem (End+1-Start)). + +offsetfaces(Fl, Offset) -> + map(fun (F) -> offsetface(F,Offset) end, Fl). + +offsetface(F, Offset) -> + map(fun (V) -> V+Offset end, F). + diff -Nru wings3d-2.2.4/plugins_src/import_export/wpc_gltf.erl wings3d-2.2.5/plugins_src/import_export/wpc_gltf.erl --- wings3d-2.2.4/plugins_src/import_export/wpc_gltf.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/import_export/wpc_gltf.erl 2019-12-07 15:15:57.000000000 +0000 @@ -105,8 +105,9 @@ end, ok = filelib:ensure_dir(filename:join(ImageDir, "dummy")), Imagetype = proplists:get_value(default_filetype, Attr, ?DEF_IMAGE_TYPE), - Contents1 = wpa:save_images(Contents0, ImageDir, Imagetype), - #e3d_file{objs=Objs, mat=Mat, creator=Creator} = Contents1, + Contents1 = e3d_file:transform(Contents0, wpa:export_matrix(Attr)), + Contents2 = wpa:save_images(Contents1, ImageDir, Imagetype), + #e3d_file{objs=Objs, mat=Mat, creator=Creator} = Contents2, GLTF0 = #{asset => #{generator => unicode:characters_to_binary(Creator), version=> <<"2.0">>}, accessors => [], diff -Nru wings3d-2.2.4/plugins_src/import_export/wpc_jscad.erl wings3d-2.2.5/plugins_src/import_export/wpc_jscad.erl --- wings3d-2.2.4/plugins_src/import_export/wpc_jscad.erl 1970-01-01 00:00:00.000000000 +0000 +++ wings3d-2.2.5/plugins_src/import_export/wpc_jscad.erl 2019-12-07 15:15:57.000000000 +0000 @@ -0,0 +1,174 @@ +%% +%% wpc_jscad.erl -- +%% +%% OpenJSCAD export. +%% +%% Copyright (c) 2019 Micheus +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%% $Id$ +%% + +-module(wpc_jscad). + +-export([init/0,menu/2,command/2]). + +-include_lib("wings/e3d/e3d.hrl"). +-include_lib("wings/intl_tools/wings_intl.hrl"). + +init() -> + true. + +menu({file,export}, Menu) -> + menu_entry(Menu); +menu({file,export_selected}, Menu) -> + menu_entry(Menu); +menu(_, Menu) -> Menu. + +command({file,{export,{jscad,Ask}}}, St) -> + Exporter = fun(Ps, Fun) -> wpa:export(Ps, Fun, St) end, + do_export(Ask, export, Exporter, St); +command({file,{export_selected,{jscad,Ask}}}, St) -> + Exporter = fun(Ps, Fun) -> wpa:export_selected(Ps, Fun, St) end, + do_export(Ask, export_selected, Exporter, St); +command(_, _) -> + next. + +menu_entry(Menu) -> + Menu ++ [{"OpenJSCAD (.jscad)...",jscad,[option]}]. + +props() -> + [{ext,".jscad"},{ext_desc,?__(1,"OpenJSCAD File")}]. + +%%% +%%% Export. +%%% + +do_export(Ask, Op, _Exporter, _St) when is_atom(Ask) -> + wpa:dialog(Ask, ?__(1,"OpenJSCAD Export Options"), dialog(export), + fun(Res) -> + {file,{Op,{jscad,Res}}} + end); +do_export(Attr, _Op, Exporter, _St) when is_list(Attr) -> + set_pref(Attr), + SubDivs = proplists:get_value(subdivisions, Attr, 0), + Ps = [{subdivisions,SubDivs}|props()], + Exporter(Ps, export_fun(Attr)). + +export_fun(Attr) -> + fun(Filename, Contents) -> + export(Filename, Contents, Attr) + end. + +export(Filename, Contents0, Attr) -> + Contents = export_transform(Contents0, Attr), + #e3d_file{objs=Objs,mat=Mat,creator=Creator} = Contents, + {ok,F} = file:open(Filename, [write]), + FName = filename:basename(Filename), + NObjs = length(Objs), + %% Write file head + io:format(F, "// File : ~ts\n",[FName]), + io:format(F, "// Objects : ~lp\n",[NObjs]), + io:format(F, "// Exported from: ~ts\n",[Creator]), + %% Write file body + io:put_chars(F, "function main() {\n"), + io:put_chars(F, " return [\n"), + try + lists:foldl(fun(#e3d_object{name = Name, obj=Obj}, AccObj) -> + Meshes = e3d_mesh:split_by_material(Obj), + NMeshes = length(Meshes), + all(fun(Mesh,AccMeshes) -> + export_object(F, Name, Mesh, Mat), + AccMeshes-1 + end, F, NMeshes, Meshes), + if AccObj > 1 -> + io:put_chars(F, ",\n"); + true -> ignore + end, + AccObj-1 + end, NObjs, Objs), + io:put_chars(F, "\n ];\n}\n") + catch _:Err:Stacktrace -> + io:format(?__(1,"OpenJSCAD Error: ~P in")++" ~p~n", [Err,30,Stacktrace]) + end, + ok = file:close(F). + +export_object(F, ObjName, #e3d_mesh{type=Type,fs=Fs,vs=VTab}, Mat_defs) -> + [#e3d_face{mat=[Material|_]}|_] = Fs, + DifColor = material(Material, Mat_defs), + io:format(F, "\t// ~ts.~ts\n",[ObjName,Material]), + io:format(F, "\tcolor(~w, \n",[DifColor]), + io:put_chars(F, "\t polyhedron(\n"), + + %% Write vertex coordinates + io:put_chars(F, "\t\t{ points: [\n"), + all(fun({X,Y,Z}) -> io:format(F, "\t\t\t[~.9f,~.9f,~.9f]", [X,Y,Z]) end,F,VTab), + io:put_chars(F, "\n\t\t\t],\n"), + %% Write vertex indexes for faces + SType = + case Type of + triangle -> "triangles"; + _ -> "polygons" + end, + io:format(F, "\t\t ~s: [\n",[SType]), + all(fun(#e3d_face{vs=Vs}) -> io:format(F, "\t\t\t~w", [lists:reverse(Vs)]) end,F,Fs), + io:put_chars(F, "\n\t\t\t]\n"), + %% Close polyhedron's data (points and triangles/polygons) + io:put_chars(F, "\t\t}\n"), + %% Close color and polyhedron + io:put_chars(F, "\t )\n"), + io:put_chars(F, "\t)"). + +material(Name, Mat_defs) -> + MatInfo = lookup(Name, Mat_defs), + Mat = lookup(opengl, MatInfo), + {Dr, Dg, Db, _} = lookup(diffuse, Mat), + [Dr, Dg, Db]. + +dialog(export) -> + [{label_column, + [{?__(1,"Swap Y and Z Axes"), + {"",get_pref(swap_y_z, true), + [{key,swap_y_z}]}}, + {export_scale_s(), + {text,get_pref(export_scale, 1.0), + [{key,export_scale},{range,{1.0,infinity}}]}}, + {?__(3,"Sub-division Steps"), + {text,get_pref(subdivisions, 0), + [{key,subdivisions},{range,{0,4}}]}} + ]} + ]. + +get_pref(Key, Def) -> + wpa:pref_get(?MODULE, Key, Def). + +set_pref(KeyVals) -> + wpa:pref_set(?MODULE, KeyVals). + +export_transform(Contents, Attr) -> + Mat = wpa:export_matrix(Attr), + e3d_file:transform(Contents, Mat). + +export_scale_s() -> ?__( 1, "Export scale"). + +%%% useful helpers +all(F, IO, [H,H1|T]) -> + F(H), + io:put_chars(IO, ",\n"), + all(F, IO, [H1|T]); +all(F, _, [H]) -> + F(H), + ok. + +all(F, IO, AccIn, [H,H1|T]) -> + Acc = F(H, AccIn), + io:put_chars(IO, ",\n"), + all(F,IO,Acc,[H1|T]); +all(F, _, AccIn, [H]) -> + F(H, AccIn). + +lookup(K, L) -> + {value, {K, V}} = lists:keysearch(K, 1, L), + V. diff -Nru wings3d-2.2.4/plugins_src/import_export/wpc_ps.erl wings3d-2.2.5/plugins_src/import_export/wpc_ps.erl --- wings3d-2.2.4/plugins_src/import_export/wpc_ps.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/import_export/wpc_ps.erl 2019-12-07 15:15:57.000000000 +0000 @@ -2,7 +2,7 @@ %% wpc_ps.erl -- %% %% Adobe PostScript (*.ps/*.eps) import based on wpc_ai.erl by Howard Trickey -%% To work, the wpc_tt plugin must also be loaded. +%% To work, the wpc_ai plugin must also be loaded. %% %% Copyright (c) 2009-2011 Richard Jones. %% 2017 Micheus (add/fixed support to Adobe Illustrator, LibreOffice, Inkscape and scribus (partial)). @@ -21,7 +21,7 @@ -define(SCALEFAC, 0.01). % amount to scale PS coords by --record(cedge,% polyarea and cedge records must match definitions in wpc_tt.erl +-record(cedge,% polyarea and cedge records must match definitions in wpc_ai.erl {vs,cp1=nil,cp2=nil,ve}). %all are {x,y} pairs -record(path, @@ -95,9 +95,9 @@ io:format("~ts: ~ts\n",[Creator, ?__(5,"Some token structures were not valid in the file and were ignored")]); true -> ok end, - Pas = [wpc_tt:findpolyareas(Cntr) || Cntr <- Cntrs], + Pas = [wpc_ai:findpolyareas(Cntr) || Cntr <- Cntrs], Pas0 = lists:flatten(Pas), - Pas1 = wpc_tt:subdivide_pas(Pas0,Nsubsteps), + Pas1 = wpc_ai:subdivide_pas(Pas0,Nsubsteps), {Vs0,Fs,HEs} = process_islands(Pas1), Center = e3d_vec:average(e3d_vec:bounding_box(Vs0)), Vec = e3d_vec:sub(e3d_vec:zero(),Center), @@ -119,7 +119,7 @@ [[lists:reverse(Cntr) || Cntr <- Cntrs] || Cntrs <- Contours, Cntrs=/=[[]]]. process_islands(Plas) -> - Process = fun(Pla) -> wpc_tt:polyareas_to_faces([Pla]) end, + Process = fun(Pla) -> wpc_ai:polyareas_to_faces([Pla]) end, Objs = lists:foldr(fun(Pla, Acc) -> %% it was noticed during the tests that some files may contain data that causes @@ -142,7 +142,7 @@ fix_slands_vertices(Objs, {I+length(Vs), {AcVs++Vs,AcFs++Fs,AcHEs++HEs}}). %% some paths definitions can contain many 'pmoveto' operators that cannot be understand -%% for the code in wpc_tt module. Then, we break them in separated objects in order to +%% for the code in wpc_ai module. Then, we break them in separated objects in order to %% provide user with most objects as he/she expect to get. break_grouped_moveto(Objs) -> [break_grouped_moveto_0(Paths,[]) || Paths <- Objs]. @@ -347,7 +347,7 @@ ps_dopathop0(CP,Pst) when CP=:="closepath"; CP=:="cp"; CP=:="h"; CP=:="cl"; CP=:="p"; CP=:="pc" -> P = Pst#pstate.curpath, Pst#pstate{curpath=P#path{close=true}}; -%% we intercept the clip new path to remove the bounding box data - it causes a crash in wpc_tt module +%% we intercept the clip new path to remove the bounding box data - it causes a crash in wpc_ai module ps_dopathop0(CP,Pst) when CP=:="clp"; CP=:="clp_npth"; CP=:="clip" -> Pst#pstate{curobjs=[]}; ps_dopathop0(_,Pst) -> Pst. diff -Nru wings3d-2.2.4/plugins_src/import_export/wpc_svg_path.erl wings3d-2.2.5/plugins_src/import_export/wpc_svg_path.erl --- wings3d-2.2.4/plugins_src/import_export/wpc_svg_path.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/import_export/wpc_svg_path.erl 2019-12-07 15:15:57.000000000 +0000 @@ -23,7 +23,7 @@ -define(SCALEFAC, 0.01). % amount to scale coords by --record(cedge,% polyarea and cedge records must match definitions in wpc_tt.erl +-record(cedge,% polyarea and cedge records must match definitions in wpc_ai.erl {vs,cp1=nil,cp2=nil,ve}). %all are {x,y} pairs -record(path, @@ -83,9 +83,9 @@ Objs = parse_bin_svg(Rest), Closedpaths = [ P || P <- Objs, P#path.close =:= true ], Cntrs = getcontours(Closedpaths), - Pas = wpc_tt:findpolyareas(Cntrs), - Pas1 = wpc_tt:subdivide_pas(Pas,Nsubsteps), - {Vs0,Fs,HEs} = wpc_tt:polyareas_to_faces(Pas1), + Pas = wpc_ai:findpolyareas(Cntrs), + Pas1 = wpc_ai:subdivide_pas(Pas,Nsubsteps), + {Vs0,Fs,HEs} = wpc_ai:polyareas_to_faces(Pas1), Center = e3d_vec:average(e3d_vec:bounding_box(Vs0)), Vec = e3d_vec:sub(e3d_vec:zero(),Center), Vs = reverse(center_object(Vec,Vs0)), diff -Nru wings3d-2.2.4/plugins_src/Makefile wings3d-2.2.5/plugins_src/Makefile --- wings3d-2.2.4/plugins_src/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -11,13 +11,14 @@ # $Id: Makefile,v 1.20 2006/01/19 22:30:47 giniu Exp $ # +include ../erl.mk + .SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ .fig .dvi .tex .class .java .pdf .psframe .pscrop ESRC=. WINGS_INTL=../intl_tools EBIN=../plugins/default -ERLC=erlc WINGS_TOP=../.. ifeq ($(TYPE),debug) @@ -46,18 +47,17 @@ common: $(TARGET_FILES) subdirs subdirs: - (cd accel; $(MAKE)) (cd import_export; $(MAKE)) (cd primitives; $(MAKE)) (cd commands; $(MAKE)) (cd autouv; $(MAKE)) template: opt - erl -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) lang: subdirs_lang template cp *.lang $(EBIN) - erl -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) subdirs_lang: (cd import_export; $(MAKE) lang) @@ -70,7 +70,6 @@ rm -f core subdirs_clean: - (cd accel; $(MAKE) clean) (cd import_export; $(MAKE) clean) (cd primitives; $(MAKE) clean) (cd commands; $(MAKE) clean) diff -Nru wings3d-2.2.4/plugins_src/primitives/Makefile wings3d-2.2.5/plugins_src/primitives/Makefile --- wings3d-2.2.4/plugins_src/primitives/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/primitives/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -10,6 +10,7 @@ # # $Id: Makefile,v 1.14 2006/08/02 22:44:40 antoneos Exp $ # +include ../../erl.mk .SUFFIXES: .erl .jam .beam .yrl .xrl .bin .mib .hrl .sgml .html .ps .3 .1 \ .fig .dvi .tex .class .java .pdf .psframe .pscrop @@ -17,7 +18,6 @@ ESRC=. WINGS_INTL=../../intl_tools EBIN=../../plugins/primitives -ERLC=erlc WINGS_TOP=../../.. WINGS_E3D=../../e3d @@ -54,11 +54,11 @@ $(MAKE) TYPE=$@ common template: opt - erl -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template_files $(EBIN) lang: template cp *.lang $(EBIN) - erl -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) + $(ERL) -pa $(WINGS_INTL) -noinput -run tools diff_lang_files $(EBIN) common: $(TARGET_FILES) diff -Nru wings3d-2.2.4/plugins_src/primitives/wpc_tt.erl wings3d-2.2.5/plugins_src/primitives/wpc_tt.erl --- wings3d-2.2.4/plugins_src/primitives/wpc_tt.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/plugins_src/primitives/wpc_tt.erl 2019-12-07 15:15:57.000000000 +0000 @@ -3,17 +3,21 @@ %% %% Functions for reading TrueType fonts (.tt) %% -%% Copyright (c) 2001-2011 Howard Trickey +%% Copyright (c) 2001-2011 Howard Trickey & Dan Gudmundsson %% %% See the file "license.terms" for information on usage and redistribution %% of this file, and for a DISCLAIMER OF ALL WARRANTIES. %% -%% $Id$ +%% Rewritten to support Unicode codepoints and more complete support of +%% the standard, and ttfc and otc files. /Dan %% +%% For TrueType format, see http://www.microsoft.com/typography/otspec/ + -module(wpc_tt). --export([init/0,menu/2,command/2,trygen/3, % trygen is for debugging - findpolyareas/1,polyareas_to_faces/1,subdivide_pas/2]). % for ai +-export([init/0,menu/2,command/2, + init_font/2, sysfontdirs/0, process_ttfs/1, trygen/3, trygen/5, ttf_info/1 % debugging + ]). % for ai -import(lists, [reverse/1,sort/2,keysearch/3,duplicate/2,nthtail/2, mapfoldl/3,foldl/3,sublist/3,map/2,last/1,seq/2,seq/3, @@ -22,32 +26,75 @@ -include_lib("wings/src/wings.hrl"). -include_lib("wings/e3d/e3d.hrl"). --record(ttfont, - {nglyph, % number of glyphs - uperem, % units per em - cmap, % 256-element tuple, maps char -> glyph - loca, % (nglyph+1)-element tuple, maps glyph -> offset in glyf - adv, % nglyph-element tuple: maps glyph -> amount to advance x - glyf}). % glyf table (binary) +-record(ttf_info, + {num_glyphs, %% Number of Glyphs + + %% Offsets to table locations + loca, glyf, head, hhea, hmtx, kern, name, os2, + cff, %% undefined or initatied CFF info + index_map, %% A Cmap mapping for out chosen character encoding + index_to_loc_format, %% Format needed to map from glyph index to glyph + file, %% Filename + collection=0, %% Collection number + data %% The binary file + }). -record(polyarea, - {boundary, %list of cedges (CCW oriented, closed) - islands=[]}). %list of lists of cedges (CW, closed) + {boundary, %%list of cedges (CCW oriented, closed) + islands=[]}). %%list of lists of cedges (CW, closed) - % a "possibly curved" edge, with explicit coords - % and optional cubic bezier control points --record(cedge, - {vs,cp1=nil,cp2=nil,ve}). %all are {x,y} pairs +-record(vertex, {pos, c, c1, type}). + +-type ttf() :: #ttf_info{}. +%% -type scale() :: Uniform::float() | {ScaleX::float(),ScaleY::float()}. +%% -type shift() :: Uniform::float() | {ShiftX::float(),ShiftY::float()}. +%% -type size() :: {Width::integer(), Height::integer()}. +-type vertex() :: #vertex{}. +-type platform() :: unicode | mac | microsoft | integer(). +-type encoding() :: unicode | roman | integer(). +-type language() :: english | integer(). %% 0 if platform is unicode -define (fsITALIC, 2#00000001). -define (fsBOLD, 2#00100000). +%% PLATFORM ID +-define(PLATFORM_ID_UNICODE, 0). +-define(PLATFORM_ID_MAC, 1). +-define(PLATFORM_ID_ISO, 2). +-define(PLATFORM_ID_MICROSOFT,3). + +%% encodingID for PLATFORM_ID_UNICODE +-define(UNICODE_EID_UNICODE_1_0 ,0). +-define(UNICODE_EID_UNICODE_1_1 ,1). +-define(UNICODE_EID_ISO_10646 ,2). +-define(UNICODE_EID_UNICODE_2_0_BMP,3). +-define(UNICODE_EID_UNICODE_2_0_FULL,4). + +%% encodingID for PLATFORM_ID_MICROSOFT +-define(MS_EID_SYMBOL ,0). +-define(MS_EID_UNICODE_BMP ,1). +-define(MS_EID_SHIFTJIS ,2). +-define(MS_EID_UNICODE_FULL ,10). + +%% encodingID for PLATFORM_ID_MAC; same as Script Manager codes +-define(MAC_EID_ROMAN ,0). +-define(MAC_EID_JAPANESE ,1). +-define(MAC_EID_CHINESE_TRAD ,2). +-define(MAC_EID_KOREAN ,3). +-define(MAC_EID_ARABIC ,4). +-define(MAC_EID_HEBREW ,5). +-define(MAC_EID_GREEK ,6). +-define(MAC_EID_RUSSIAN ,7). + -define(S16, 16/signed). -define(U16, 16/unsigned). -define(S32, 32/signed). -define(U32, 32/unsigned). -define(SKIP, _/binary). +-define(DBG(F,A), ok). +%-define(DBG(F,A), io:format("~w:~w: "++ F, [?MODULE,?LINE] ++ A)). + init() -> true. menu({shape}, Menu) -> @@ -68,7 +115,7 @@ command(_, _) -> next. make_text(Ask, St) when is_atom(Ask) -> - FontDir = sysfontdir(), + FontDirs = sysfontdirs(), DefFont = default_font(), FontInfo = case wpa:pref_get(wpc_tt, fontname, DefFont) of FI = #{type := font} -> FI; @@ -77,7 +124,7 @@ Text = wpa:pref_get(wpc_tt, text, "Wings 3D"), Bisect = wpa:pref_get(wpc_tt, bisections, 0), - GbtFonts = process_ttfs(FontDir), + GbtFonts = process_ttfs(FontDirs), %% io:format("FontList: ~p\n\n",[gb_trees:to_list(GbtFonts)]), Dlg = [{vframe, [ @@ -87,334 +134,120 @@ help_button() ]}, {label_column, [ - {?__(5,"Number of edge bisections"),{slider,{text,Bisect,[{key,{wpc_tt,bisections}},{range,{0,3}}]}}}, + {?__(5,"Number of edge bisections"),{slider,{text,Bisect,[{key,{wpc_tt,bisections}},{range,{0,4}}]}}}, {?__(3,"TrueType font"), {fontpicker,FontInfo,[{key,{wpc_tt,font}}]}}]}, wings_shapes:transform_obj_dlg() ],[{margin,false}] }], - wings_dialog:dialog(Ask,?__(1,"Create Text"), {preview, Dlg}, - fun({dialog_preview,[T,N,{_,Ctrl},RX,RY,RZ,MX,MY,MZ,Grnd]=_Res}) -> - {_, FPath} = get_font_file(GbtFonts,Ctrl), - {preview,{shape,{text,[T,N,{fontdir,FPath},RX,RY,RZ,MX,MY,MZ,Grnd]}},St}; - (cancel) -> - St; - ([T,N,{_,WxFont},RX,RY,RZ,MX,MY,MZ,Grnd]=_Res) when is_tuple(WxFont) -> - {NewFontI, FPath} = get_font_file(GbtFonts,WxFont), - wpa:pref_set(wpc_tt, fontname, NewFontI), - wpa:pref_set(wpc_tt, text, element(2,T)), - wpa:pref_set(wpc_tt, bisections, element(2,N)), - {commit,{shape,{text,[T,N,{fontdir,FPath},RX,RY,RZ,MX,MY,MZ,Grnd]}},St} - end); - -make_text([{_,T},{_,N},{_,DirFont}|Transf], _) -> - F = filename:basename(DirFont), - D = filename:dirname(DirFont), - gen(F, D, T, N, Transf). + Fun = fun({dialog_preview,[T,N,{_,Ctrl},RX,RY,RZ,MX,MY,MZ,Grnd]=_Res}) -> + {NewFontI, FPath} = find_font_file(GbtFonts,Ctrl), + Size = maps:get(size, NewFontI), + {preview,{shape,{text,[T,N,{fontdir,FPath},Size,RX,RY,RZ,MX,MY,MZ,Grnd]}},St}; + (cancel) -> + St; + ([T,N,{_,WxFont},RX,RY,RZ,MX,MY,MZ,Grnd]=_Res) when is_tuple(WxFont) -> + {NewFontI, FPath} = find_font_file(GbtFonts,WxFont), + Size = maps:get(size, NewFontI), + case FPath of + {_, undefined} -> + St; + _ -> + wpa:pref_set(wpc_tt, fontname, NewFontI), + wpa:pref_set(wpc_tt, text, element(2,T)), + wpa:pref_set(wpc_tt, bisections, element(2,N)), + {commit,{shape,{text,[T,N,{fontdir,FPath},Size,RX,RY,RZ,MX,MY,MZ,Grnd]}},St} + end + end, + wings_dialog:dialog(Ask,?__(1,"Create Text"), {preview, Dlg}, Fun); + +make_text([{_,T},{_,N},{_,FontFile}, Size|Transf], _) -> + %% Assuming 10 ppi is 2 w.u. which the other primitives are + gen(FontFile, T, N, Size*0.25, Transf). help_button() -> - Title = ?__(1,"Browsing for Fonts on Windows"), + Title = ?__(1,"Select font"), TextFun = fun () -> help() end, {help,Title,TextFun}. help() -> - [?__(1,"Only TrueType fonts can be used and they must be" - "installed in standard operating system directory for fonts.")]. + [?__(1,"Only TrueType (OpenType) fonts can be used \n" + "and they must be installed in the standard operating system\n" + "directories for fonts.")]. -gen(_Font, _Dir, "", _Nsubsteps, _Transf) -> +gen(_FontFile, "", _Nsubsteps, _, _Transf) -> keep; -gen(Font, Dir, Text, Nsubsteps, Transf) -> - File = font_file(Font, Dir), - case catch trygen(File, Text, Nsubsteps) of +gen({FName,undefined}, _, _, _, _) -> + Msg = ?__(1,"Text failed: failed to locate the TTF file for: " ++ FName), + wpa:error_msg(Msg); +gen({_FName, {File, Idx}}, Text, Nsubsteps, Size, Transf) -> + try trygen(File, Text, Idx, Nsubsteps, Size) of {new_shape,Name,Fs0,Vs0,He} -> Vs = wings_shapes:transform_obj(Transf, Vs0), Fs = [#e3d_face{vs=Vsidx} || Vsidx <- Fs0], Mesh = #e3d_mesh{type=polygon, vs=Vs, fs=Fs, he=He}, {new_shape, Name, #e3d_object{obj=Mesh}, []}; - {error,"no such file or directory"} -> - wpa:error_msg(?__(4,"Text failed: failed to locate the TTF file for the selected font")); - {error,Reason} -> - wpa:error_msg(?__(1,"Text failed: ") ++ Reason); - X -> - io:format(?__(2,"caught error: ") ++"~p~n", [X]), + keep -> + keep + catch + throw:{error, _What, Msg}:_St -> + %% ?DBG("error: ~p ~p~n ~P~n", [_What, Msg, _St, 40]), + wpa:error_msg(Msg); + _:X:ST -> + io:format(?__(2,"caught error: ") ++"~P~nST:~p", [X, 40,ST]), wpa:error_msg(?__(3,"Text failed: internal error")) end. -process_ttfs(Dir) -> - Add = fun(FileName, Acc) -> - case read_ttf_name(FileName) of - {FName,FStyle,FWeight} -> - gb_trees:enter({FName,FStyle,FWeight},FileName,Acc); - _ -> Acc - end - end, - filelib:fold_files(Dir, ".ttf|.TTF", true, Add, gb_trees:empty()). - -read_ttf_name(File) -> - case file:read_file(File) of - {ok,Filecontents} -> - try - {ok, TTFpart} = ttfpart(Filecontents), - (TTFpart =:= Filecontents) orelse erlang:display({changed, File}), - parsett_header(File, Filecontents) - catch _ET:_Error -> - io:format("ERROR: ~s:~P ~p~n",[File,_Error, 10, erlang:get_stacktrace()]), - error - end; - {error,_Reason} -> - error - end. - -%% The Font dialog can show options for styles which we are not able to find a file like the "Italic" -%% and "Italic Bold" for "Comic Sans MS" - there are no files for these styles (only Regular and -%% Bold). Here, we'll try to get the font with a most close appearance of that selected by the user. -get_font_file(GbtFonts, WxFont) -> - FontInfo = wings_text:get_font_info(WxFont), - #{face:=FName, style:=FStyle, weight:=FWeight} = FontInfo, - case get_font_file(0,GbtFonts,FName,FStyle,FWeight) of - undefined -> {FontInfo, "unknown"}; - FPath -> {FontInfo, FPath} - end. - -get_font_file(0=Try,GbtFonts,FName,FStyle,FWeight) -> % try to get the right fount - case gb_trees:lookup({FName,FStyle,FWeight},GbtFonts) of - {value,FPath} -> FPath; - _ -> get_font_file(Try+1,GbtFonts,FName,normal,FWeight) - end; -get_font_file(1=Try,GbtFonts,FName,FStyle,FWeight) -> % try to get the right fount - case gb_trees:lookup({FName,FStyle,FWeight},GbtFonts) of - {value,FPath} -> FPath; - _ -> get_font_file(Try+1,GbtFonts,FName,FStyle,normal) - end; -get_font_file(2,GbtFonts,FName,FStyle,FWeight) -> % try to get the right fount - case gb_trees:lookup({FName,FStyle,FWeight},GbtFonts) of - {value,FPath} -> FPath; - _ -> win_font_substitutes(FName,GbtFonts) - end. - -parsett_header(_File, Bin) -> - FontInfo = font_info(Bin), - Family = proplists:get_value(family, FontInfo, undefined), - {Style, Weight} = font_styles(Bin), - %% io:format("File: ~p ~p ~p ~p~n", [_File,Family, Style, Weight]), - {Family,Style,Weight}. - -%% Return the requested string from font -%% By default font family and subfamily (if not regular) -font_info(Font) -> - StdInfoItems = [info(1),info(2),info(3),info(4),info(16),info(17)], - Try = [{StdInfoItems, microsoft, unicode, english}, - {StdInfoItems, unicode, unicode, 0}, - {StdInfoItems, mac, roman, english} - ], - font_info_2(Font, Try). - -font_info_2(Font, [{Id,Platform,Enc,Lang}|Rest]) -> - case font_info(Font, Id, Platform, Enc, Lang) of - [] -> font_info_2(Font, Rest); - Info -> Info - end; -font_info_2(_, []) -> []. - - -%% Return the requested string from font -%% Info Items: 1,2,3,4,16,17 may be interesting -%% Returns a list if the encoding is known otherwise a binary. -%% Return the empty list is no info that could be matched is found. -font_info(Bin, Id, Platform, Encoding, Language) -> - case find_table(Bin, <<"name">>) of - false -> []; - Name -> - <<_:Name/binary, _:16, Count:?U16, StringOffset:?U16, FI/binary>> = Bin, - <<_:Name/binary, _:StringOffset/binary, Strings/binary>> = Bin, - get_font_info(Count, FI, Strings, Id, Platform, Encoding, Language) - end. - -get_font_info(0, _, _, _, _, _, _) -> []; -get_font_info(N, <>, Strings, - WIds, WPlatform, WEnc, WLang) -> - <<_:StrOffset/binary, String:Length/binary, ?SKIP>> = Strings, - Platform = platform(PId), - Encoding = encoding(EId, Platform), - Lang = language(LId, Platform), - Enc = check_enc(Encoding, WEnc), - case lists:member(info(NId), WIds) of - true when Platform =:= WPlatform, Enc, Lang =:= WLang -> - %% io:format("Encoding ~p Platform ~p Eid ~p~n",[Encoding, Platform, EId]), - [{info(NId), string(String, Encoding)}| - get_font_info(N-1, Rest, Strings, WIds, WPlatform, WEnc, WLang)]; - _ -> - get_font_info(N-1, Rest, Strings, WIds, WPlatform, WEnc, WLang) - end. - -check_enc(A, A) -> true; -check_enc({unicode,_}, unicode) -> true; -check_enc({unicode,_,_}, unicode) -> true; -check_enc(_, _) -> false. - -find_table(<<_:32, NumTables:?U16, _SR:16, _ES:16, _RS:16, Tables/binary>>, Tag) -> - find_table(NumTables, Tag, Tables). - -find_table(0, _, _) -> false; -find_table(_, Tag, <>) -> - Offset; -find_table(Num, Tag, <<_Tag:4/binary, _CheckSum:32, _Offset:32, _Len:32, Next/binary>>) -> - find_table(Num-1, Tag, Next). - -platform(0) -> unicode; -platform(1) -> mac; -platform(2) -> iso; -platform(3) -> microsoft; -platform(Id) -> Id. - -encoding(0, unicode) -> {unicode, {1,0}}; -encoding(1, unicode) -> {unicode, {1,1}}; -encoding(2, unicode) -> iso_10646; -encoding(3, unicode) -> {unicode, bmp, {2,0}}; -encoding(4, unicode) -> {unicode, full,{2,0}}; - -encoding(0, microsoft) -> symbol; -encoding(1, microsoft) -> {unicode, bmp}; -encoding(2, microsoft) -> shiftjis; -encoding(10, microsoft) -> {unicode, bmp}; - -encoding(0, mac) -> roman ; -encoding(1, mac) -> japanese ; -encoding(2, mac) -> chinese_trad ; -encoding(3, mac) -> korean ; -encoding(4, mac) -> arabic ; -encoding(5, mac) -> hebrew ; -encoding(6, mac) -> greek ; -encoding(7, mac) -> russian ; - -encoding(Id, _) -> Id. - -language(16#0409, microsoft) -> english ; -language(16#0804, microsoft) -> chinese ; -language(16#0413, microsoft) -> dutch ; -language(16#040c, microsoft) -> french ; -language(16#0407, microsoft) -> german ; -language(16#040d, microsoft) -> hebrew ; -language(16#0410, microsoft) -> italian ; -language(16#0411, microsoft) -> japanese; -language(16#0412, microsoft) -> korean ; -language(16#0419, microsoft) -> russian ; -%%language(16#0409, microsoft) -> spanish ; -language(16#041d, microsoft) -> swedish ; - -language(0 , mac) -> english ; -language(12, mac) -> arabic ; -language(4 , mac) -> dutch ; -language(1 , mac) -> french ; -language(2 , mac) -> german ; -language(10, mac) -> hebrew ; -language(3 , mac) -> italian ; -language(11, mac) -> japanese; -language(23, mac) -> korean ; -language(32, mac) -> russian ; -language(6 , mac) -> spanish ; -language(5 , mac) -> swedish ; -language(33, mac) -> chinese_simplified ; -language(19, mac) -> chinese ; - -language(Id, _) -> Id. - -info(0) -> copyright; -info(1) -> family; -info(2) -> subfamily; -info(3) -> unique_subfamily; -info(4) -> fullname; -info(5) -> version; -info(6) -> postscript_name; -info(7) -> trademark_notice; -info(8) -> manufacturer_name; -info(9) -> designer; -info(10) -> description; -info(11) -> url_vendor; -info(12) -> url_designer; -info(13) -> license_descr; -info(14) -> url_license; -%%info(15) -> reserved; -info(16) -> preferred_family; -info(17) -> preferred_subfamily; -%%info(18) -> compatible_full; %% Mac only -info(19) -> sample_text; -info(Id) -> Id. - -string(String, roman) -> - unicode:characters_to_list(String, latin1); -string(String, {unicode, _}) -> - unicode:characters_to_list(String, utf16); -string(String, {unicode, bmp, _}) -> - unicode:characters_to_list(String, utf16); -string(String, {unicode, full, _}) -> - unicode:characters_to_list(String, utf32); -string(String, _) -> - String. - -font_styles(Bin) -> - TabOffset = find_table(Bin, <<"OS/2">>), - case is_integer(TabOffset) of - true -> - <<_:TabOffset/binary,_Ver:16,_Pad:30/binary,_Panose:10/binary,_ChrRng:16/binary, - _VenId:4/binary,FsSel:16,_T1/binary>> = Bin, - - FStyle = if (FsSel band ?fsITALIC) =:= ?fsITALIC -> italic; - true -> normal - end, - FWeight = if (FsSel band ?fsBOLD) =:= ?fsBOLD -> bold; - true -> normal - end, - {FStyle, FWeight}; - false -> - {normal, normal} - end. - -trygen(File, Text, Nsubsteps) -> - case file:read_file(File) of - {ok,Filecontents} -> - case ttfpart(Filecontents) of - {ok, TTFpart} -> - Ttf = parsett(TTFpart), - Pa = getpolyareas(Text, Ttf, Nsubsteps), - {Vs0,Fs,He} = polyareas_to_faces(Pa), - {CX,CY,CZ} = e3d_vec:average(tuple_to_list(e3d_bv:box(Vs0))), - Vs = [{X-CX,Y-CY,Z-CZ} || {X,Y,Z} <- Vs0], - {new_shape,"text",Fs,Vs,He}; % Would be nicer centered by centroid - _ -> {error, ?__(1,"Can't find TrueType section in ") ++ File} - end; - {error,Reason} -> - {error,file:format_error(Reason)} - end. - -%% Try to map a Name to a font file using registry -%% and in any case, concatenate Dir in front of it (if Dir != ""). -%% If dir is empty and we didn't find it in the current dir, -%% try the sysfontdir again. -font_file(Name, Dir) -> - Name1 = case Dir of - "." -> Name; - _ -> filename:join([Dir,Name]) - end, - case filelib:is_regular(Name1) of - true -> Name1; - _ -> - case os:type() of - {win32,_} -> - Name2 = case winregval(?__(1,"Fonts"), Name ++ " (TrueType)") of - none -> Name; - Fname -> Fname - end, - case Dir of - "." -> filename:join([sysfontdir(),Name2]); - _ -> filename:absname(Dir ++ "\\" ++ Name2) - end; - _ -> - case Dir of - "." -> filename:join([sysfontdir(),Name]); - _ -> Name1 - end - end +process_ttfs(Dirs) -> + case ets:info(?MODULE) of + undefined -> + Tab = ets:new(?MODULE, [named_table, public]), + Add = fun(FileName, _Acc) -> + Store = fun(FontInfo, Idx) -> + case ets:lookup(Tab, FontInfo) of + [] -> ok; + [_Old] -> + %% ?DBG("Overwrite Font Info:~nOLD: ~p~nNEW: ~p~n", + %% [_Old,{FontInfo,{FileName,Idx}}]), + ok + end, + true = ets:insert(Tab, {FontInfo,{FileName,Idx}}), + Idx+1 + end, + try ttf_info(FileName) of + error -> ok; + List -> lists:foldl(Store, 0, List), ok + catch _:_What:_St -> + ?DBG("Fail: ~p : ~P~n ~P~n",[FileName, _What, 20, _St, 20]), + ok + end + end, + Filter = ".ttf|.TTF|.ttc|.TTC|.otf|.OTF", + lists:foldl(fun(Dir, Tree) -> + filelib:fold_files(Dir, Filter, true, Add, Tree) + end, ok, Dirs), + Tab; + [_|_] -> + ?MODULE + end. + +ttf_info(File) -> + {ok,Filecontents} = file:read_file(File), + find_font_info(Filecontents, File). + +trygen(File, Text, SubDiv) -> + trygen(File, Text, 0, SubDiv, 2). +trygen(File, Text, Idx, Nsubsteps, Size) -> + TTF = init_font(File, Idx), + ?DBG("~P~n",[TTF, 20]), + Pa0 = get_polyareas(Text, TTF, Nsubsteps, Size), + {Vs0,Fs,He} = polyareas_to_faces(Pa0), + case Vs0 of + [_|_] -> + {CX,_CY,CZ} = e3d_vec:average(tuple_to_list(e3d_bv:box(Vs0))), + Vs = [{X-CX,Y,Z-CZ} || {X,Y,Z} <- Vs0], + {new_shape,"text: " ++ Text,Fs,Vs,He}; + [] -> + keep end. %% Look up value with Name in Windows registry, @@ -448,50 +281,33 @@ none end. -win_font_substitutes(FName,GbtFonts) -> - case os:type() of - {win32,_} -> - case winregval(?__(1,"FontSubstitutes"),FName) of - none -> undefined; - FSName -> - case gb_trees:lookup({FSName,normal,normal},GbtFonts) of - {value, FPath} -> FPath; - _ -> undefined - end - end; - _ -> undefined - end. - %% Try to find default system directory for fonts -sysfontdir() -> - case os:type() of - {win32,Wintype} -> - SR = case winregval("", "SystemRoot") of - none -> - case Wintype of - nt -> "C:/winnt"; - _ -> "C:/windows" - end; - Val -> Val - end, - SR ++ "/Fonts"; - {unix,Utype} -> - Dir = case Utype of - darwin -> "/Library/Fonts"; - _ -> "/usr/share/fonts/" - end, - case file:list_dir(Dir) of - {error, _} -> - "/~"; - _ -> - Dir - end - end. +sysfontdirs() -> + sysfontdirs(os:type()). + +sysfontdirs({win32,Wintype}) -> + Def = case Wintype of + nt -> "C:/winnt"; + _ -> "C:/windows" + end, + System = case winregval("", "SystemRoot") of + none -> Def; + Val -> Val + end, + UserInstalled = filename:join(filename:basedir(user_data, "Microsoft"), "Windows"), + [filename:join(System, "Fonts"), filename:join(UserInstalled, "Fonts")]; +sysfontdirs({unix,darwin}) -> + Home = os:getenv("HOME"), + ["/Library/Fonts", filename:join(Home, "Library/Fonts")]; +sysfontdirs({unix,_}) -> + Home = os:getenv("HOME"), + ["/usr/share/fonts/", "/usr/local/share/fonts", + filename:join(Home, ".fonts"), + filename:join(Home, ".local/share/fonts")]. default_font() -> wings_text:get_font_info(wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT)). - %% Return {Vs,Fs} corresponding to list of polyareas, %% where Vs is list of coords and Fs is list of list of %% coord indices, describing faces. @@ -520,28 +336,6 @@ build_hard_edges([Last], First, All) -> [{Last, First}|All]. - -%% For TrueType format, see -%% http://developer.apple.com/fonts/TTRefMan/index.html -%% or -%% http://www.microsoft.com/typography/otspec/ - -ttfpart(Filecontents) -> - case is_ttf(Filecontents, false) of - true -> - {ok,Filecontents}; - _ -> - io:format("Not a ttf file", []), - case parse_dfont(Filecontents) of - <<>> -> - case parse_embedded_ttf(Filecontents) of - <<>> -> {error,<<>>}; - B -> {ok, B} - end; - B -> {ok, B} - end - end. - %% ttf fonts start with an "offset subtable": %% uint32 - tag to mark as TTF (one of the 0,1,0,0; "true"; or "OTTO") %% uint16 - number of directory tables @@ -549,606 +343,127 @@ %% uint16 - entry selector: log2(maximum power of 2 <= numTables) %% uint16 - range shift: numTables*16-searchRange -is_ttf(<<0,1,0,0,R/binary>>, V) -> not V orelse is_ttf_fin(R); -is_ttf(<<"true",R/binary>>, V) -> not V orelse is_ttf_fin(R); -%is_ttf(<<"typ1",R/binary>>, V) -> not V orelse is_ttf_fin(R); -is_ttf(<<"OTTO",R/binary>>, V) -> not V orelse is_ttf_fin(R); -is_ttf(<<1,0,0,0,R/binary>>, V) -> not V orelse is_ttf_fin(R); -is_ttf(_,_) -> false. - -is_ttf_fin(<>) -> - NumTabs > 0 andalso (size(B) > NumTabs*16) andalso (Rsh == NumTabs*16 - SrchRng). - -%% An Apple "dfont" has many resources -%% in a merged resource fork/data fork. -%% Some of those resources may be 'sfnt's, and those may by ttf format. -%% We could parse the resource map, find all the 'sfnt's, parse the -%% name/map at the end, and give the user a choice. -%% For now, just do the easy thing: source through all the resources -%% until find one that starts like a ttf font. -parse_dfont(<>) -> - Skip = Rpos - 16, - case (Mpos == Rpos + Rlen) andalso (Rlen > 0) - andalso (Skip >= 0) andalso (Skip < size(B)) of - true -> - <<_Skipped:Skip/binary, B2/binary>> = B, - findttfres(B2, Rlen); - _ -> <<>> +is_font(<<1,0,0,0,?SKIP>>) -> true; %% Truetype 1 +is_font(<<"typ1",?SKIP>>) -> true; %% Truetype with type 1 font, not supported +is_font(<<"OTTO",?SKIP>>) -> true; %% OpenType with CFF +is_font(<<0,1,0,0,?SKIP>>) -> true; %% OpenType with 1.0 +is_font(_) -> false. + +%% is_ttfc(<<"ttcf", ?SKIP>>) -> true; +%% is_ttfc(_) -> false. + +get_polyareas(Text, Font, Nsubsteps, Size) -> + Scale = scale_for_mapping_em_to_pixels(Font, Size), + Area = fun(CodePoint, {X,Acc}) -> + Glyph = find_glyph_index(Font, CodePoint), + {Advance, _} = get_glyph_h_metrics(Font, Glyph), + %% ?DBG("Char: ~c Glyph: ~w Advance: ~p*~p=~p~n", + %% [CodePoint, Glyph, Advance, Scale, Advance*Scale]), + Areas = get_polyarea(Glyph, X, Scale, Nsubsteps, Font), + %% ?DBG(" ~150.p~n", [Areas]), + {X+Advance, Areas ++ Acc} + end, + {_, Pas} = lists:foldl(Area, {0, []}, Text), + Pas. + +get_polyarea(Glyph, X, Scale, Nsubsteps, Font) -> + GlyphVs = get_glyph_shape(Font, Glyph), + VsCont = verts_to_point_lists(GlyphVs, Scale, Nsubsteps), + {_X0,_Y0,_X1,_Y1} = bb_box(VsCont), + %% io:format("~p:~p: ~p ~p~n",[?MODULE,?LINE, Scale, get_glyph_box(Font, Glyph)]), + + Make = fun(Vs) -> make_edges(Vs, {Scale,Scale}, {X*Scale, 0}, []) end, + Edges = lists:map(Make, VsCont), + %% io:format("~p:~p: Edges: ~n ~w~n",[?MODULE,?LINE, Edges]), + findpolyareas(Edges). + +verts_to_point_lists(Vs, Scale, SubDiv) -> + F = {8.0/(Scale*math:pow(8,SubDiv)), SubDiv}, + lists:reverse(verts_to_points(Vs, {0.0,0.0}, F, [], [])). + +verts_to_points([#vertex{type=move,pos=Point}|Vs], _, F, Cont, All) -> + verts_to_points(Vs, Point, F, [Point], add_contour(Cont, All)); +verts_to_points([#vertex{type=line,pos=Point}|Vs], _, F, Cont, All) -> + verts_to_points(Vs, Point, F, [Point|Cont], All); +verts_to_points([#vertex{type=curve,pos=VP={PX,PY},c={CX,CY}}|Vs], + {X,Y}, {Limit,Level}=F, Cont0, All) -> + Cont = tesselate_curve(X,Y, CX,CY, PX,PY, Limit, Level, Cont0), + verts_to_points(Vs, VP, F, Cont, All); +verts_to_points([#vertex{type=cubic,pos=VP={PX,PY},c={CX,CY}, c1={CX1,CY1}}|Vs], + {X,Y}, {Limit,Level}=F, Cont0, All) -> + Cont = tesselate_cubic(X,Y, CX,CY, CX1,CY1, PX,PY, Limit, Level, Cont0), + verts_to_points(Vs, VP, F, Cont, All); +verts_to_points([], _, _, Cont, All) -> + add_contour(Cont, All). + +add_contour([], All) -> All; +add_contour(Cont, All) -> + [lists:reverse(Cont)|All]. + +tesselate_curve(X0,Y0, X1,Y1, X2,Y2, Limit, Level, Cont0) when Level >= 0 -> + Mx = (X0 + 2*X1 + X2)/4.0, + My = (Y0 + 2*Y1 + Y2)/4.0, + %% Versus Directly Drawn Line + Dx = (X0+X2)/2.0 - Mx, + Dy = (Y0+Y2)/2.0 - My, + %% io:format(" ~p,~p ~p,~p => ~p > ~p~n",[X0,Y0,X1,Y1,Dx*Dx+Dy*Dy,Limit]), + if (Dx*Dx+Dy*Dy) > Limit -> + Cont1 = tesselate_curve(X0,Y0, (X0+X1)/2.0,(Y0+Y1)/2.0, Mx,My, Limit, Level-1, Cont0), + tesselate_curve(Mx,My, (X1+X2)/2.0,(Y1+Y2)/2.0, X2,Y2, Limit, Level-1, Cont1); + true -> + [{X2,Y2}|Cont0] end; -parse_dfont(_) -> <<>>. +tesselate_curve(_X0,_Y0, _X1,_Y1, X2,Y2, _F, _Level, Cont) -> + [{X2,Y2}|Cont]. -findttfres(_, 0) -> <<>>; -findttfres(<>, Rlenleft) -> - if - (size(B) >= Reslen) -> - <> = B, - case is_ttf(Res, true) of - true -> Res; - _ -> findttfres(B2, Rlenleft - Reslen - 4) - end; - true -> <<>> +tesselate_cubic(X0,Y0, X1,Y1, X2,Y2, X3,Y3, Limit, Level, Cont0) when Level >= 0 -> + Dx0 = X1-X0, Dy0 = Y1-Y0, + Dx1 = X2-X1, Dy1 = Y2-Y1, + Dx2 = X3-X2, Dy2 = Y3-Y2, + Dx = X3-X0, Dy = Y3-Y0, + + LL = math:sqrt(Dx0*Dx0+Dy0*Dy0)+math:sqrt(Dx1*Dx1+Dy1*Dy1)+math:sqrt(Dx2*Dx2+Dy2*Dy2), + SL = math:sqrt(Dx*Dx+Dy*Dy), + + if (LL*LL-SL*SL) > Limit -> + X01 = (X0+X1)/2, Y01 = (Y0+Y1)/2, + X12 = (X1+X2)/2, Y12 = (Y1+Y2)/2, + X23 = (X2+X3)/2, Y23 = (Y2+Y3)/2, + + Xa = (X01+X12)/2, Ya = (Y01+Y12)/2, + Xb = (X12+X23)/2, Yb = (Y12+Y23)/2, + + Mx = (Xa+Xb)/2, My = (Ya+Yb)/2, + + Cont1 = tesselate_cubic(X0,Y0, X01,Y01, Xa,Ya, Mx,My, Limit, Level-1, Cont0), + tesselate_cubic(Mx,My, Xb,Yb, X23,Y23, X3,Y3, Limit, Level-1, Cont1); + true -> + [{X3,Y3}|Cont0] end; -findttfres(_, _) -> {nil, << 0 >> }. - -%% This is a desperation move to handle files whose format -%% we don't know, but might have a valid ttf section inside it -%% (some old Mac files are like this). -%% Just go byte-by-byte, looking for the start of a valid ttf section. -parse_embedded_ttf(B) -> - case is_ttf(B, true) of - true -> B; - _ -> - case B of - <<_C,Brest/binary>> -> parse_embedded_ttf(Brest); - _ -> <<>> - end - end. - -%% Parse binary arg, which should be a TrueType file, -%% and return a ttfont. -%% Throws {error,reason} or a badmatch if the file format is wrong. - % -%% After the offset table (see above), comes a number of table -%% directories (each with a 4-character tag), and then the binary -%% data making up the tables themselves (the directories have pointers -%% into the binary data). -%% We parse all the directories (Dirs), then use that to get all the -%% tables as binaries (Tabs), and then finally parse the tables we need. -%% The actual polygons are in the glyf table, which is parsed for -%% only the needed glyfs, later. -parsett(<<_C1,_C2,_C3,_C4,Ntabs:16/unsigned,_Srchrng:16/unsigned, - _Esel:16/unsigned,_Rngshift:16/unsigned,B/binary>>) -> - {Dirs,B1} = getdirs(Ntabs,B), - Dirs1 = sort(fun({X,_,_},{Y,_,_}) -> X < Y end, Dirs), - Offset = 12 + (Ntabs*16), - Tabs = gettabs(Dirs1, B1, Offset), - Nglyph = parsemaxptab(Tabs), - Cmap = parsecmaptab(Tabs), - {Uperem, ShortLoca} = parseheadtab(Tabs), - Loca = parselocatab(Tabs, Nglyph, ShortLoca), - Nhmetrics = parsehheatab(Tabs), - Adv = parsehmtxtab(Tabs, Nglyph, Nhmetrics), - Glyf = findtab("glyf", Tabs), - #ttfont{nglyph=Nglyph, uperem=Uperem, cmap=Cmap, loca=Loca, adv=Adv, glyf=Glyf}; -parsett(_) -> - throw({error,?__(1,"Bad offset table")}). - -%% returns list of table directory entries: {offset,length,name} tuples -getdirs(Ntabs,B) -> getdirs(Ntabs,B,[]). - -%% Table directory format: -%% uint32 - tag (4 ascii chars identifying table kind) -%% uint32 - checksum for this table -%% uint32 - offset from beginning of ttf font -%% uint32 - length of table in bytes (actual, not padded) -getdirs(0, B, Acc) -> - {reverse(Acc),B}; -getdirs(Nleft,<>,Acc) -> - getdirs(Nleft-1, B, [{Off,Len,[W,X,Y,Z]} | Acc]); -getdirs(_,_,_) -> - throw({error,?__(1,"Bad dir format")}). - -%% returns list of {tablename,table/binary} tuples -gettabs(Dirs,B,Offset) -> gettabs(Dirs,B,Offset,[]). - -gettabs([],_,_,Acc) -> - reverse(Acc); -gettabs([{Offnext,Len,Nam}|T]=Dirs,B,Off,Acc) -> - if - Off == Offnext -> - <> = B, - gettabs(T, B1, Off+Len, [{Nam,Tab} | Acc]); - Off < Offnext -> - Padlen = Offnext - Off, - <<_C:Padlen/binary,B1/binary>> = B, - gettabs(Dirs,B1,Offnext,Acc); - true -> - throw({error,?__(1,"Bad table offsets/sizes")}) - end. - -%% Find the table with the given name in Tabs and return it. -%% Throw error if not found. -findtab(Name, Tabs) -> - case keysearch(Name, 1, Tabs) of - {value, {_, Tab}} -> - Tab; - false -> - throw({error,?__(1,"No ") ++ Name ++ ?__(2," table")}) - end. - - -%% Parse the "maxp" (Maximum Profile) tab of Tabs and return numGlyphs -parsemaxptab(Tabs) -> - Tab = findtab("maxp", Tabs), - <<16#00010000:32,NumGlyphs:16/unsigned,_/binary>> = Tab, - NumGlyphs. - -%% Parse the "cmap" (Character to Glyph Index) tab of Tabs. -%% Return 256-long tuple where element (c+1) is glyph number for character c. -%% -%% cmap table format: -%% uint16 - version (should be 0) -%% uint16 - number of subtables -%% followed by the subtables, each in format: -%% uint16 - platformID -%% uint16 - platform-specific encoding id -%% uint32 - offset of mapping table -%% -%% Currently, we can handle format 0 (single byte table) -%% and format 4 (two-byte, segmented encoding format) -parsecmaptab(Tabs) -> - Tab = findtab("cmap", Tabs), - <<0:16,Nsubtabs:16,T1/binary>> = Tab, - ST = getcmapsubtabs(Nsubtabs, T1, Tab, []), - SortST = sort(fun cmapcmp/2, ST), - case SortST of - [{_P,_E,0,Off}|_] -> - list_to_tuple(binary_to_list(Tab,Off+1+6,Off+256+6)); - [{_P,_E,4,Off}|_] -> - cmapf4(Tab, Off); - _ -> throw({error,?__(1,"No suitable character map")}) - end. - -getcmapsubtabs(0, _, _, Acc) -> - Acc; -getcmapsubtabs(N, <>, Tab, Acc) -> - {Fhigh,Flow} = list_to_tuple(binary_to_list(Tab,Off+1,Off+2)), - Format = toushort(Fhigh,Flow), - getcmapsubtabs(N-1, T, Tab, [{Pid,Eid,Format,Off}|Acc]). - -%% Need a format 0 or 4 table, -%% prefer Platform 0 (Unicode), -%% and prefer Platform specific encodoing 1 in both cases -cmapcmp({P1,E1,F1,_},{P2,E2,F2,_}) -> - if - F1 == 0, F2 /= 0 -> true; - F2 == 0 -> false; - F1 == 4, F2 /= 4 -> true; - F2 == 4 -> false; - true -> - if - P1 < P2 -> true; - P1 > P2 -> false; - true -> - if - E1 == 1 -> true; - E2 == 1 -> false; - true -> E1 < E2 - end - end - end. - -%% Format 4 cmap subtables have this format: -%% uint16 - format (will be 4) -%% uint16 - length in bytes of subtable -%% uint16 - language -%% uint16 - segcountX2 : 2 * segment count -%% uint16 - searchRange : 2 * (2**Floor(log2 segcount)) -%% uint16 - entrySelector : log2(searchRange/2) -%% uint16 - rangeShift : (2 * segCount) - searchRange -%% uint16 * segCount : endCode[]: ending character code for each seg, FFFF last -%% uint16 - reserved pad -%% uint16 * segCount : startCode[]: starting character code for each seg -%% uint16 * segCount : idDelta[]: delta for all character codes in seg -%% uint16 * segCount : idRangeOffset[]: offset in bytes to glyph index array or 0 -%% uint16 * variable : Glyph index array -%% -%% segments are sorted in increasing endCode value -cmapf4(Tab, Off) -> - <<_Before:Off/binary,4:16,Len:16,_Lang:16,SegcountX2:16,_SrchRng:16, - _EntSel:16,_RngSh:16, - BEnds:SegcountX2/binary,_Pad:16,BStarts:SegcountX2/binary, - BDeltas:SegcountX2/binary, T/binary>> = Tab, - N = SegcountX2 div 2, - NGI = (Len - 8*2 - 4*SegcountX2) div 2, - {Ends,_} = takeushorts(N, binary_to_list(BEnds)), - {Starts,_} = takeushorts(N, binary_to_list(BStarts)), - {Deltas,_} = takeushorts(N, binary_to_list(BDeltas)), - {ROffsGlinds,_} = takeushorts(N+NGI, binary_to_list(T,1,SegcountX2+2*NGI)), - docmapf4(1,N,Ends,Starts,Deltas,ROffsGlinds,0,[]). - -docmapf4(I,N,_Ends,_Starts,_Deltas,_ROffsGlinds,Alen,Acc) - when (I > N) or (Alen >= 256) -> - fincmapf4(Acc,Alen); -docmapf4(I,N,Ends,Starts,Deltas,ROffsGlinds,Alen,Acc) -> - E = element(I,Ends), - S = element(I,Starts), - D = element(I,Deltas), - R = element(I,ROffsGlinds), - E2 = if E > 255 -> 255; true -> E end, - S2 = if S >= Alen -> S; true -> Alen end, - case (S2 >= 256) or (S2 > E2) of - true -> fincmapf4(Acc,Alen); - false -> - Padlen = S2 - Alen, - Pad = lists:duplicate(Padlen, 0), - Mplen = E2-S2+1, - Mpart = - case R of - 0 -> cmapf4r0(E2,S2,D); - 16#FFFF -> lists:duplicate(Mplen,0); - _ -> cmapf4rx(E2,S2,D,R,I,ROffsGlinds) - end, - Acc2 = lists:append([Acc,Pad,Mpart]), - Alen2 = Alen + Padlen + Mplen, - docmapf4(I+1,N,Ends,Starts,Deltas,ROffsGlinds,Alen2,Acc2) - end. - -fincmapf4(Acc,Alen) when Alen == 256 -> list_to_tuple(Acc); -fincmapf4(Acc,Alen) when Alen < 256 -> - Padlen = 256 - Alen, - list_to_tuple(Acc ++ lists:duplicate(Padlen,0)); -fincmapf4(Acc,Alen) when Alen > 256 -> - list_to_tuple(lists:sublist(Acc, 1, 256)). - -%% offset 0 case of format 4: just add D to [S, S+1, ..., E] -%% to get mapped glyphs. -cmapf4r0(E,S,D) -> [ ushortmod(K+D) || K <- lists:seq(S,E) ]. - -%% offset !0 case of format 4: have to look at offset table and glyph -%% index table as concatenated; add offset (as byte count) to current -%% address of current place in the offset table, then look at the -%% ushort there, and if not zero, add delta to it to get mapped glyph -%% for S. Continue through until get mapped glyph for E. -cmapf4rx(E,S,D,R,I,T) when S =< E -> - J = I + (R div 2), - IDXS = [ element(J+K, T) || K <- lists:seq(0,E-S) ], - map(fun (A) -> if A == 0 -> 0; true -> ushortmod(A+D) end end, IDXS); -cmapf4rx(_,_,_,_,_,_) -> []. - -ushortmod(X) -> X rem 65536. - -%% Parse the "head" (Font Header) tab of Tabs and return {units-per-em, shortloca} -%% where shortloca is true if loca table uses "short" format -parseheadtab(Tabs) -> - Tab = findtab("head", Tabs), - <<16#00010000:32,_Frev:32,_Csuma:32,16#5F0F3CF5:32, - _Flags:16,Uperem:16,_Dcreat:64,_Dmod:64, - _Xmin:16,_Ymin:16,_Xmax:16,_Ymax:16, - _Macsty:16,_LRP:16,_Fdir:16,IndToLocFmt:16,0:16>> = Tab, - {Uperem, case IndToLocFmt of 0 -> true; _ -> false end}. - -%% Parse the "loca" tab of Tabs and return an (Nglyph+1)-element tuple -%% mapping a glyph index into an offset in the glyf table. -%% ShortLoca is true for the "short" format, false otherwise. -parselocatab(Tabs, Nglyph, ShortLoca) -> - Tab = findtab("loca", Tabs), - case ShortLoca of - true -> - locashort(Nglyph+1,Tab,[]); - false -> - localong(Nglyph+1,Tab,[]) - end. - -%% short format: unsigned shorts divided by two are in table -locashort(0,_,Acc) -> - list_to_tuple(reverse(Acc)); -locashort(N,<>,Acc) -> - locashort(N-1, T, [2*X|Acc]). - -localong(0,_,Acc) -> - list_to_tuple(reverse(Acc)); -localong(N,<>,Acc) -> - localong(N-1, T, [X|Acc]). - -%% Parse the "hhea" (Horizontal Header) tab of Tabs and return numberOfHMetrics -parsehheatab(Tabs) -> - Tab = findtab("hhea", Tabs), - <<16#00010000:32,_Asc:16,_Desc:16,_Lgap:16,_Awmax:16, - _Minlsb:16,_Minrsb:16,_Xmaxext:16,_Cslrise:16,_Cslrun:16, - _Res:10/binary, 0:16, NumberOfHMetrics:16/unsigned>> = Tab, - NumberOfHMetrics. - -%% Parse the "hmtx" (Horizontal Metrics) tab of Tabs and return an Nglyph-element tuple -%% mapping a glyph index into the amound (in FUnits) to advance in the x-direction -%% after "printing" the glyph. -parsehmtxtab(Tabs, Nglyph, Nhmetrics) -> - Tab = findtab("hmtx", Tabs), - hmtx(Nglyph, Nhmetrics, Tab, []). - -%% need to repeat last element if Nhmetrics goes to zero before Nglyph -hmtx(0, _, _, Acc) -> - list_to_tuple(reverse(Acc)); -hmtx(Nglyph, Nhmetrics, <>, Acc) -> - Acc1 = [Aw | Acc], - Ng1 = Nglyph-1, - Nh1 = Nhmetrics-1, - if - Nh1 == 0, Ng1 > 0 -> - list_to_tuple(reverse(Acc) ++ duplicate(Ng1, Aw)); - true -> - hmtx(Ng1, Nh1, T, Acc1) - end. - -getpolyareas(Text, Ttf, Nsubsteps) -> - Pas = getpolyareas(Text, Ttf, 0, []), - Pas1 = clean_pas(Pas), - subdivide_pas(Pas1, Nsubsteps). - -getpolyareas([], _, _, Acc) -> - flatten(reverse(Acc)); -getpolyareas([C|Rest], #ttfont{nglyph=Ng,adv=Adv,cmap=Cmap}=Ttf, X, Acc) -> - {X1,Acc1} = - case (C >= 0) and (C < 256) of - true -> - G = element(C+1, Cmap), - if - G < Ng -> - Xnew = X + element(G+1, Adv), - case glyphpolyareas(G, Ttf, X) of - nil -> - {Xnew, Acc}; - Pa -> - {Xnew, [Pa|Acc]} - end; - true -> - {X, Acc} - end; - false -> - {X, Acc} - end, - getpolyareas(Rest, Ttf, X1, Acc1). +tesselate_cubic(_X0,_Y0, _X1,_Y1, _X2,_Y2, X3,Y3, _F, _Level, Cont) -> + [{X3,Y3}|Cont]. -%% Get contours for glyph G (known to be in range 0..nglyph-1). -%% Return nil if no data or no contours for glyph G. -%% -%% Format of glyph data: -%% uint16 - number of contours (-1 means this is made of other chars, 0 means no data) -%% FWord - xmin -%% FWord - ymin -%% FWord - xmax -%% FWord - ymax -%% then comes data for simple or compound glyph - -glyphpolyareas(G, #ttfont{loca=Loca,glyf=Glyf,uperem=Uperem}, X) -> - Off = element(G+1, Loca), - Len = element(G+2, Loca) - Off, - if - Len < 9 -> - nil; - true -> - Gdat = binary_to_list(Glyf, Off+1, Off+Len), - [Nch,Ncl|T1] = Gdat, - Ncont = toushort(Nch, Ncl), - if - Ncont == 0 -> - nil; - Ncont == 65535 -> - nil; - true -> - %% Calculate scale so Em box measures 2 by 2 - %% (about the scale of wings primatives) - Scale = 2.0/float(Uperem), - gpa(nthtail(4*2, T1), Ncont, X, Scale) - end - end. -%% continue glyphpolyareas, when there are > 0 contours -%% (Gdat is now at start of endPtsOfContours array) -%% format expected for Gdat: -%% uint16 * number_of_contours : endPtsOfContours array (entries are point indices) -%% (the total number of points is one more than last entry in that array) -%% uint16 : instruction length, the number of bytes used for instructions -%% uint8 * instruction_length : the instructions for this glyph -%% uint8 * (variable) : flags array: one per point, or less, if repeats -%% (uint8 | uint16) sequence : x coordinates (each could be one or two bytes or same as prev) -%% (uint8 | uint16) sequence : y coordinates (each could be one or two bytes or same as prev) -%% -%% flag bits: -%% 0 (1) : on curve (if set, point is on curve, else it's off) -%% 1 (2) : x-short (if set, x coord is one byte and x-same bit gives sign, -%% % else x coord is two bytes unless x-same bit is set - then xcoord -%% % is omitted because it is same as previous x coord) -%% 2 (4) : y-short -%% 3 (8) : repeat (if set, next byte gives count: repeat this flag count times after) -%% 4 (16): x-same (used in conjunction with x-short) -%% 5 (32): y-same - -gpa(Gdat, Ncont, Xorg, Scale) -> - {Eoc,T1} = takeushorts(Ncont,Gdat), - Npt = element(Ncont, Eoc)+1, - [Ninstrh,Ninstrl | T2] = T1, - Ninstr = toushort(Ninstrh,Ninstrl), - T3 = nthtail(Ninstr,T2), - {Flags,T4} = gflags(Npt, T3), - {X0,T5} = gcoords(Npt, T4, Flags, 2, 16), - {Y0,_} = gcoords(Npt, T5, Flags, 4, 32), - X = makeabs(X0, Xorg, Scale), - Y = makeabs(Y0, 0, Scale), - Cntrs = contours(Ncont, Eoc, X, Y, Flags), - Ccntrs = map(fun getcedges/1, Cntrs), - Ccntrs1 = lists:filter(fun (V) -> length(V) > 2 end, Ccntrs), - findpolyareas(Ccntrs1). - -%% For debugging -%% dumpsvg(X,Y) -> dsvg(X,Y,[],[], 0). -%% dsvg([],[],Plist,Vlist,_) -> -%% Vtab = list_to_tuple(reverse(Vlist)), -%% Ps = [reverse(Plist)], -%% wpc_svg:write_polys("try.svg", Ps, Vtab); -%% dsvg([X1 | XR], [Y1 | YR], Pl, Vl, I) -> -%% dsvg(XR, YR, [I|Pl], [{X1,Y1}|Vl], I + 1). - -%% Take N pairs of bytes off of L, convert each pair to ushort, -%% return {tuple of the ushorts, remainder of L}. -takeushorts(N,L) -> takeushorts(N,L,[]). - -takeushorts(0, L, Acc) -> - {list_to_tuple(reverse(Acc)), L}; -takeushorts(N, [B1,B2 | Rest], Acc) -> - takeushorts(N-1, Rest, [toushort(B1,B2) | Acc]). - -%% Get N glyph flags from L and return {list of flags, rest of L}. -%% Less than N flags might come off of L because if a flag has the -%% repeat bit (8) set, the next byte is used as a repeat count. -gflags(N,L) -> gflags(N,L,[]). - -gflags(0, L, Acc) -> - {reverse(Acc), L}; -gflags(N, [F|Rest], Acc) -> - Acc1 = [F | Acc], - if - (F band 8) == 8 -> %% repeat F next-byte more times - [Rep|Rest2] = Rest, - Acc2 = duplicate(Rep,F) ++ Acc1, - gflags(N-1-Rep, Rest2, Acc2); - true -> - gflags(N-1, Rest, Acc1) - end. - -%% Get N glyph coords from L and return {list of coords, rest of L}. -%% The coords are relative-to-previous at this point. -%% The Flags list controls how next coord comes off of L: -%% if Sbit is set, it's one byte (and Rbit is set if positive), else 2 bytes. -%% if Sbit isn't set, Rbit set means value is same as previous (relative offset = 0) -gcoords(N,L,Flags,Sbit,Rbit) -> gcoords(N,L,Flags,Sbit,Rbit,[]). - -gcoords(0,L,_,_,_,Acc) -> - {reverse(Acc), L}; -gcoords(N,L,[F|Tf],Sbit,Rbit,Acc) -> - SRbits = Sbit bor Rbit, - case F band SRbits of - 0 -> - [B1,B2|Tl] = L, - gcoords(N-1, Tl, Tf, Sbit, Rbit, [tosshort(B1,B2)|Acc]); - SRbits -> - [B|Tl] = L, - gcoords(N-1, Tl, Tf, Sbit, Rbit, [B|Acc]); - Sbit -> - [B|Tl] = L, - gcoords(N-1, Tl, Tf, Sbit, Rbit, [-B|Acc]); - Rbit -> - gcoords(N-1, L, Tf, Sbit, Rbit, [0|Acc]) - end. - -toushort(B1,B2) -> B1*256 + B2. - -tosshort(B1,B2) -> - <> = list_to_binary([B1,B2]), - A. - -%% Change coords in L to be absolute (starting at V) rather than relative. -%% Also, after translation, make into a float and scale by Scale -makeabs(L, V, Scale) -> - {Labs, _} = mapfoldl(fun (Z,Pos) -> - Znew = Z+Pos, - {Scale*float(Znew),Znew} - end, V, L), - Labs. - -%% Return list of Ncont {list of x-coords, list of y-coords, flags} tuples, -%% where each is a sublist of X, Y, Flags, as directed by Eoc tuple. -contours(Ncont, Eoc, X, Y, Flags) -> contours(Ncont, 1, 1, Eoc, X, Y, Flags, []). - -contours(0, _, _, _, _, _, _, Acc) -> - reverse(Acc); -contours(Ncont, I, Start, Eoc, X, Y, Flags, Acc) -> - End = element(I, Eoc) + 1, - Len = End - Start + 1, - X1 = sublist(X, Start, Len), - Y1 = sublist(Y, Start, Len), - F1 = sublist(Flags, Start, Len), - contours(Ncont-1, I+1, End+1, Eoc, X, Y, Flags, [{X1,Y1,F1}|Acc]). - -%% Turn the parallel lists (X,Y,Flags), representing a TrueType glyph, -%% into a list of cedges. -%% We have to turn a quadratic B-spline into a list of cubic bezier curves. -getcedges({X,Y,Flags}) -> - N = length(X), - if - N >= 3 -> - getcedges(X, Y, Flags, X, Y, Flags, []); - true -> - [] - end. - -%% Quadratic B-spline edges go between "on-curve" points with one -%% intermediate "off-curve" control point. But if the points don't -%% alternate on-off-on-off..., you can derive the missing ones by -%% averaging their neighbors. -%% Looking at the on-curve/off-curve status of the head three points -%% on the lists, there are six cases to worry about: -%% i i+1 i+2 -%% a. off on - -%% .........+ dotted line shows edge assumed previously added -%% -%% b. on off on qedge {i,i+1,i+2} -%% +______o_____+ underscore shows edge between + points, with 'o' ctl -%% -%% c. on off off qedge {i,I+1,avg(i+1,i+2)} -%% +_____o___* edge goes to interpolated '*' point -%% -%% d. on on - line {i,i+1} -%% +_____+ straight edge -%% -%% e. off off on qedge {avg(i,i+1),i+1,i+2} -%% ......*__o_____+ incoming edge assumed previously added -%% -%% f. off off off qedge {avg(i,i+1),i+1,avg(i+1,i+2)} -%% ......*__o__* +bb_box(ListOfLists) -> + MinMax = fun({X,Y}, {MinX,MinY,MaxX,MaxY}) -> + {min(X,MinX), min(Y,MinY), + max(X,MaxX), max(X,MaxY)} + end, + lists:foldl(fun(List, Acc) -> lists:foldl(MinMax, Acc, List) end, {0.0,0.0, 0.0,0.0}, ListOfLists). + +make_edges([{JX,JY}|Rest=[{KX,KY}|_]], Scale = {ScX, ScY}, Shift = {ShX,ShY}, Eds) -> + Edge = {{JX * ScX + ShX, JY * ScY + ShY}, + {KX * ScX + ShX, KY * ScY + ShY}}, + case Edge of + {V,V} -> %% Remove zero size edges + make_edges(Rest, Scale, Shift, Eds); + _ -> + make_edges(Rest, Scale, Shift, [Edge|Eds]) + end; +make_edges(_, _, _, Eds) -> + lists:reverse(Eds). -getcedges([], [], [], _, _, _, Acc) -> - reverse(Acc); -getcedges([_|Xt]=X, [_|Yt]=Y, [_|Ft]=Flags, Xo, Yo, Fo, Acc) -> - {Cur,Ison} = nthptandison(1, X, Y, Flags, Xo, Yo, Fo), - {Next,Isnexton} = nthptandison(2, X, Y, Flags, Xo, Yo, Fo), - {Anext,Isanexton} = nthptandison(3, X, Y, Flags, Xo, Yo, Fo), - case (not(Ison) and Isnexton) of - true -> - %% this case generates no segment - getcedges(Xt, Yt, Ft, Xo, Yo, Fo, Acc); - _ -> - {Curon,Ctl,Nexton} = - case {Ison,Isnexton,Isanexton} of - {true,false,true} -> {Cur,Next,Anext}; - {true,false,false} -> {Cur,Next,avg(Next,Anext)}; - {true,true,_} -> {Cur,nil,Next}; - {false,false,true} -> {avg(Cur,Next),Next,Anext}; - {false,false,false} -> {avg(Cur,Next),Next,avg(Next,Anext)} - end, - %% Ctl, if not nil, is quadratic Bezier control point. - %% Following uses degree-elevation theory to get cubic cps. - {Cp1,Cp2} = case Ctl of - nil -> {nil, nil}; - _ -> {lininterp(2.0/3.0, Curon, Ctl), - lininterp(2.0/3.0, Nexton, Ctl)} - end, - Edge = #cedge{vs=Curon, cp1=Cp1, cp2=Cp2, ve=Nexton}, - getcedges(Xt, Yt, Ft, Xo, Yo, Fo, [Edge|Acc]) - end. - -avg({X1,Y1},{X2,Y2}) -> {0.5*(X1+X2), 0.5*(Y1+Y2)}. - -lininterp(F,{X1,Y1},{X2,Y2}) -> {(1.0-F)*X1 + F*X2, (1.0-F)*Y1 + F*Y2}. - -%% Return {Nth point, is-on-curve flag} based on args -%% (use (Xo,Yo,Fo), the original list, when need to wrap). -nthptandison(1, [X|_], [Y|_], [F|_], _Xo, _Yo, _Fo) -> - {{X,Y}, if (F band 1) == 1 -> true; true -> false end}; -nthptandison(N, [_|Xt], [_|Yt], [_|Ft], Xo, Yo, Fo) -> - nthptandison(N-1, Xt, Yt, Ft, Xo, Yo, Fo); -nthptandison(N, [], _, _, Xo, Yo, Fo) -> - nthptandison(N, Xo, Yo, Fo, Xo, Yo, Fo). +%%% %% Cconts is list of "curved contours". %% Each curved contour is a list of cedges, representing a closed contour. @@ -1239,7 +554,7 @@ true, seq(1,N)). ccarea(Ccont) -> - 0.5 * foldl(fun (#cedge{vs={X1,Y1},ve={X2,Y2}},A) -> + 0.5 * foldl(fun ({{X1,Y1},{X2,Y2}},A) -> A + X1*Y2 - X2*Y1 end, 0.0, Ccont). @@ -1260,69 +575,19 @@ revccont(C) -> reverse(map(fun revcedge/1, C)). %% reverse a cedge -revcedge(#cedge{vs=Vs,cp1=Cp1,cp2=Cp2,ve=Ve}) -> - #cedge{vs=Ve,cp1=Cp2,cp2=Cp1,ve=Vs}. +revcedge({Vs,Ve}) -> {Ve,Vs}. %% classify vertices of contour B with respect to contour A. %% return {# inside A, # on A}. -classifyverts(A,B) -> foldl(fun (#cedge{vs=Vb},Acc) -> cfv(A,Vb,Acc) end, - {0,0}, B). +classifyverts(A,B) -> + foldl(fun({Vb,_},Acc) -> cfv(A,Vb,Acc) end, {0,0}, B). -%% Subdivide (bisect each each) Nsubsteps times. -%% When bezier edges are subdivided, the inserted point goes -%% at the proper place on the curve. -subdivide_pas(Pas,0) -> Pas; -subdivide_pas(Pas,Nsubsteps) -> - map(fun (Pa) -> subdivide_pa(Pa,Nsubsteps) end, Pas). - -subdivide_pa(Pa, 0) -> - Pa; -subdivide_pa(#polyarea{boundary=B,islands=Isls}, N) -> - subdivide_pa(#polyarea{boundary=subdivide_contour(B), - islands=map(fun subdivide_contour/1, Isls)}, N-1). - -subdivide_contour(Cntr) -> - flatten(map(fun (CE) -> subdivide_cedge(CE,0.5) end, Cntr)). - -%% subdivide CE at parameter Alpha, returning two new CE's in list. -subdivide_cedge(#cedge{vs=Vs,cp1=nil,cp2=nil,ve=Ve},Alpha) -> - Vm = lininterp(Alpha, Vs, Ve), - [#cedge{vs=Vs,ve=Vm}, #cedge{vs=Vm,ve=Ve}]; -subdivide_cedge(#cedge{vs=Vs,cp1=C1,cp2=C2,ve=Ve},Alpha) -> - B0 = {Vs,C1,C2,Ve}, - B1 = bezstep(B0,1,Alpha), - B2 = bezstep(B1,2,Alpha), - B3 = bezstep(B2,3,Alpha), - [#cedge{vs=element(1,B0),cp1=element(1,B1),cp2=element(1,B2),ve=element(1,B3)}, - #cedge{vs=element(1,B3),cp1=element(2,B2),cp2=element(3,B1),ve=element(4,B0)}]. - -bezstep(B,R,Alpha) -> - list_to_tuple(bzss(B,0,3-R,Alpha)). - -bzss(_B,I,Ilim,_Alpha) when I > Ilim -> []; -bzss(B,I,Ilim,Alpha) -> - [lininterp(Alpha,element(I+1,B),element(I+2,B)) | bzss(B,I+1,Ilim,Alpha)]. - -%% Clean up all the polygons in the polyarea list Pas. -%% "Clean" means remove zero-length edges. -clean_pas(Pas) -> map(fun clean_pa/1, Pas). - -clean_pa(#polyarea{boundary=B,islands=Isls}) -> - #polyarea{boundary=clean_contour(B), - islands=map(fun clean_contour/1, Isls)}. - -clean_contour([]) -> []; -clean_contour([CE=#cedge{vs=Vs,ve=Ve} | T]) -> - case Vs==Ve of - true -> clean_contour(T); - _ -> [CE | clean_contour(T)] - end. %% Decide whether vertex P is inside or on (as a vertex) contour A, %% and return modified pair. Assumes A is CCW oriented. %% CF Eric Haines ptinpoly.c in Graphics Gems IV cfv(A,P,{Inside,On}) -> - #cedge{vs=Va0} = last(A), + {Va0, _} = last(A), if Va0 == P -> {Inside, On+1}; @@ -1337,7 +602,7 @@ vinside([], _V0, _P, Inside, _Yflag0) -> Inside; -vinside([#cedge{vs={X1,Y1}=V1}|Arest], {X0,Y0}, P={Xp,Yp}, Inside, Yflag0) -> +vinside([{{X1,Y1}=V1,_}|Arest], {X0,Y0}, P={Xp,Yp}, Inside, Yflag0) -> if V1 == P -> on; @@ -1422,7 +687,7 @@ Fs = Ft ++ Fb ++ Fsides, {Vs,Fs, [ F#e3d_face.vs || F <- Fs1 ++ Fs2]}. -cel2vec(Cel, Z) -> map(fun (#cedge{vs={X,Y}}) -> {X,Y,Z} end, Cel). +cel2vec(Cel, Z) -> map(fun({{X,Y},_}) -> {X,Y,Z} end, Cel). faces(Nlist,Org,Kind) -> faces(Nlist,Org,Kind,[]). @@ -1452,3 +717,1235 @@ offsetface(F, Offset) -> map(fun (V) -> V+Offset end, F). +%%%%%%%%%%%%%%%%%%%%% TTF PARSER %%%%%%%%%%%%%%%%%%%% + + +%% Heavily inspired from Sean Barret's code @ nothings.org (see stb_truetype.h) +%% +%% @doc +%% Codepoint +%% Characters are defined by unicode codepoints, e.g. 65 is +%% uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +%% the hiragana for "ma". +%% +%% Glyph +%% A visual character shape (every codepoint is rendered as +%% some glyph) +%% +%% Glyph index +%% A font-specific integer ID representing a glyph +%% +%% Baseline +%% Glyph shapes are defined relative to a baseline, which is the +%% bottom of uppercase characters. Characters extend both above +%% and below the baseline. +%% +%% Current Point +%% As you draw text to the screen, you keep track of a "current point" +%% which is the origin of each character. The current point's vertical +%% position is the baseline. Even "baked fonts" use this model. +%% +%% Vertical Font Metrics +%% The vertical qualities of the font, used to vertically position +%% and space the characters. See docs for get_font_v_metrics. +%% +%% Font Size in Pixels or Points +%% The preferred interface for specifying font sizes in truetype +%% is to specify how tall the font's vertical extent should be in pixels. +%% If that sounds good enough, skip the next paragraph. +%% +%% Most font APIs instead use "points", which are a common typographic +%% measurement for describing font size, defined as 72 points per inch. +%% truetype provides a point API for compatibility. However, true +%% "per inch" conventions don't make much sense on computer displays +%% since they different monitors have different number of pixels per +%% inch. For example, Windows traditionally uses a convention that +%% there are 96 pixels per inch, thus making 'inch' measurements have +%% nothing to do with inches, and thus effectively defining a point to +%% be 1.333 pixels. Additionally, the TrueType font data provides +%% an explicit scale factor to scale a given font's glyphs to points, +%% but the author has observed that this scale factor is often wrong +%% for non-commercial fonts, thus making fonts scaled in points +%% according to the TrueType spec incoherently sized in practice. +%% + +%% Each .ttf/.ttc file may have more than one font. Each font has a +%% sequential index number starting from 0. A regular .ttf file will +%% only define one font and it always be at index 0. + +platform(0) -> unicode; +platform(1) -> mac; +platform(2) -> iso; +platform(3) -> microsoft; +platform(Id) -> Id. + +encoding(0, unicode) -> {unicode, {1,0}}; +encoding(1, unicode) -> {unicode, {1,1}}; +encoding(2, unicode) -> iso_10646; +encoding(3, unicode) -> {unicode, bmp, {2,0}}; +encoding(4, unicode) -> {unicode, full,{2,0}}; + +encoding(0, microsoft) -> symbol; +encoding(1, microsoft) -> {unicode, bmp}; +encoding(2, microsoft) -> shiftjis; +encoding(10, microsoft) -> {unicode, bmp}; + +encoding(0, mac) -> roman ; +encoding(1, mac) -> japanese ; +encoding(2, mac) -> chinese_trad ; +encoding(3, mac) -> korean ; +encoding(4, mac) -> arabic ; +encoding(5, mac) -> hebrew ; +encoding(6, mac) -> greek ; +encoding(7, mac) -> russian ; + +encoding(Id, _) -> Id. + +language(0 , mac) -> english ; +language(12, mac) -> arabic ; +language(4 , mac) -> dutch ; +language(1 , mac) -> french ; +language(2 , mac) -> german ; +language(10, mac) -> hebrew ; +language(3 , mac) -> italian ; +language(11, mac) -> japanese; +language(23, mac) -> korean ; +language(32, mac) -> russian ; +language(6 , mac) -> spanish ; +language(5 , mac) -> swedish ; +language(33, mac) -> chinese_simplified ; +language(19, mac) -> chinese ; + +language(16#0409, microsoft) -> english ; +language(16#0804, microsoft) -> chinese ; +language(16#0413, microsoft) -> dutch ; +language(16#040c, microsoft) -> french ; +language(16#0407, microsoft) -> german ; +language(16#040d, microsoft) -> hebrew ; +language(16#0410, microsoft) -> italian ; +language(16#0411, microsoft) -> japanese; +language(16#0412, microsoft) -> korean ; +language(16#0419, microsoft) -> russian ; +%%language(16#0409, microsoft) -> spanish ; +language(16#041d, microsoft) -> swedish ; +language(Id, _) -> Id. + +info(0) -> copyright; +info(1) -> family; +info(2) -> subfamily; +info(3) -> unique_subfamily; +info(4) -> fullname; +info(5) -> version; +info(6) -> postscript_name; +info(7) -> trademark_notice; +info(8) -> manufacturer_name; +info(9) -> designer; +info(10) -> description; +info(11) -> url_vendor; +info(12) -> url_designer; +info(13) -> license_descr; +info(14) -> url_license; +%info(15) -> reserved; +info(16) -> preferred_family; +info(17) -> preferred_subfamily; +%%info(18) -> compatible_full; %% Mac only +info(19) -> sample_text; +info(Id) -> Id. + +-spec init_font(FileName, Index) -> ttf() when + FileName :: list(), + Index :: integer(). +init_font(Filename, Index) -> + case file:read_file(Filename) of + {ok, Bin} -> init_font_1(Filename, Bin, Index); + {error,Error} -> throw({error, Error, "Couldn't open file" ++ Filename}) + end. + +init_font_1(Filename, Bin0, Index) -> + Bin = get_font_from_offset(Bin0, Index), + is_font(Bin) orelse throw({error, bad_ttf_file, ?__(1,"Unsupported ttf format")}), + Tabs = find_tables(Bin), + try + CMap = maps:get(<<"cmap">>, Tabs), + %% Either loca and glyf + Loca = maps:get(<<"loca">>, Tabs, undefined), + Glyf = maps:get(<<"glyf">>, Tabs, undefined), + %% or CFF is needed + Cff = maps:get(<<"CFF ">>, Tabs, undefined), + Head = maps:get(<<"head">>, Tabs), + Hhea = maps:get(<<"hhea">>, Tabs), + Hmtx = maps:get(<<"hmtx">>, Tabs), + Kern = maps:get(<<"kern">>, Tabs, undefined), + Name = maps:get(<<"name">>, Tabs), + Os2 = maps:get(<<"OS/2">>, Tabs, undefined), + NumGlyphs = num_glyphs(maps:get(<<"maxp">>, Tabs, undefined), Bin0), + IndexMap = find_index_map(CMap, Bin0), + CffMap = pp_cff(Cff, Bin0, Filename), + (Loca == undefined orelse Glyf == undefined) + andalso CffMap == undefined + andalso throw({error, no_glyf_info, ?__(1,"Unsupported ttf format")}), + Skip = Head+50, + <<_:Skip/binary, LocFormat:?U16, ?SKIP>> = Bin0, + #ttf_info{data = Bin0, file = Filename, collection = Index, + name = Name, os2 = Os2, + num_glyphs = NumGlyphs, + loca = Loca, glyf = Glyf, + cff = CffMap, + head = Head, hhea = Hhea, + hmtx = Hmtx, kern = Kern, + index_map = IndexMap, + index_to_loc_format = LocFormat + } + catch error:_Err:_ST -> + io:format("Parse error: ~p~n~P:~n ~P~n",[Filename, _Err,30,_ST, 100]), + throw({error, unsupport_ttf_format, ?__(1, "Unsupported ttf format")}) + end. + +find_font_info(<<"ttcf", 0,_V,0,0, N:32, ?SKIP >> = Bin, File) -> + %% ?DBG("Version ~w: Size ~w~n",[V,N]), + Info = fun(Idx, Acc) -> + try + Font = init_font_1(File, Bin, Idx), + [find_font_info_1(Font)|Acc] + catch throw:_ -> + Acc + end + end, + lists:foldl(Info, [], lists:seq(0,N-1)); +find_font_info(Bin, File) -> + try init_font_1(File, Bin,0) of + Font -> + [find_font_info_1(Font)] + catch throw:_ -> + [] + end. + +find_font_info_1(#ttf_info{file=_File, collection=_Coll} = TTF) -> + FontInfo = font_info(TTF), + Family = proplists:get_value(family, FontInfo, undefined), + PrefFamily = proplists:get_value(preferred_family, FontInfo, undefined), + {Style, Weight} = font_styles(TTF), + %% ?DBG("~s (~w) ~p ~s ~s~n ~0.p~n~n", [_File, _Coll, Family, Style, Weight, FontInfo]), + %% io:format("File: ~p ~p ~p ~p~n", [_File,Family, Style, Weight]), + case PrefFamily of + undefined -> {Family,Style,Weight}; + Family -> {Family,Style,Weight}; + _ -> {PrefFamily,Style,Weight} + end. + +check_enc(A, A) -> true; +check_enc({unicode,_}, unicode) -> true; +check_enc({unicode,_,_}, unicode) -> true; +check_enc(_, _) -> false. + +string(String, roman) -> + unicode:characters_to_list(String, latin1); +string(String, {unicode, _}) -> + unicode:characters_to_list(String, utf16); +string(String, {unicode, bmp, _}) -> + unicode:characters_to_list(String, utf16); +string(String, {unicode, full, _}) -> + unicode:characters_to_list(String, utf32); +string(String, _) -> + String. + +font_styles(#ttf_info{data=Bin, os2=TabOffset}) when is_integer(TabOffset) -> + <<_:TabOffset/binary,_Ver:16,_:16,Weight:?U16,_Pad:26/binary, + _Panose:10/binary,_ChrRng:16/binary,_VenId:4/binary, + FsSel:16,_T1/binary>> = Bin, + FStyle = if (FsSel band ?fsITALIC) =:= ?fsITALIC -> italic; + true -> normal + end, + FWeight = if + Weight < 150 -> light; %% thin + Weight < 250 -> light; %% extra-light + Weight < 350 -> light; + Weight < 450 -> normal; + Weight < 550 -> normal; %% medium + Weight < 650 -> bold; %% semi-bold + Weight < 750 -> bold; + Weight < 850 -> bold; %% extra-bold + true -> bold %% black + end, + {FStyle, FWeight}; +font_styles(_) -> + {normal, normal}. + +%% Return the requested string from font +%% By default font family and subfamily (if not regular) +font_info(Font) -> + StdInfoItems = [info(1),info(2),info(3),info(4),info(16),info(17)], + Try = [{StdInfoItems, microsoft, unicode, english}, + {StdInfoItems, unicode, unicode, 0}, + {StdInfoItems, mac, roman, english} + ], + font_info_2(Font, Try). + +font_info_2(Font, [{Id,Platform,Enc,Lang}|Rest]) -> + case font_info(Font, Id, Platform, Enc, Lang) of + [] -> font_info_2(Font, Rest); + Info -> Info + end. + +%% Return the requested string from font +%% Info Items: 1,2,3,4,16,17 may be interesting +%% Returns a list if the encoding is known otherwise a binary. +%% Return the empty list is no info that could be matched is found. +-spec font_info(Font::ttf(), + [InfoId::integer()], + Platform::platform(), + Encoding::encoding(), + Language::language()) -> [{InfoId::integer, string()}]. +font_info(#ttf_info{data=Bin, name=Name}, Id, Platform, Encoding, Language) -> + <<_:Name/binary, _V:16, Count:?U16, StringOffset:?U16, FI/binary>> = Bin, + <<_:Name/binary, _:StringOffset/binary, Strings/binary>> = Bin, + get_font_info(Count, FI, Strings, Id, Platform, Encoding, Language). + +get_font_info(0, _, _, _, _, _, _) -> []; +get_font_info(N, <>, Strings, + WIds, WPlatform, WEnc, WLang) -> + <<_:StrOffset/binary, String:Length/binary, ?SKIP>> = Strings, + Platform = platform(PId), + Encoding = encoding(EId, Platform), + Lang = language(LId, Platform), + Enc = check_enc(Encoding, WEnc), + case lists:member(info(NId), WIds) of + true when Platform =:= WPlatform, Enc, Lang =:= WLang -> + [{info(NId), string(String, Encoding)}| + get_font_info(N-1, Rest, Strings, WIds, WPlatform, WEnc, WLang)]; + _ -> + get_font_info(N-1, Rest, Strings, WIds, WPlatform, WEnc, WLang) + end. + +get_font_from_offset(<<"ttcf", 0,_V,0,0, N:32, Rest0/binary >> = Bin, Index) + when N > Index -> + Pos = Index*4, + <<_:Pos/binary, Offset:32, ?SKIP>> = Rest0, + <<_:Offset/binary, TTF/binary>> = Bin, + TTF; +get_font_from_offset(Bin, 0) -> + Bin. + +find_font_file(Table, WxFont) -> + FontInfo = wings_text:get_font_info(WxFont), + try + #{face:=FName, style:=FStyle, weight:=FWeight} = FontInfo, + TryList = [{Style,Weight} || Style <- [FStyle,normal], Weight <- [FWeight,normal]], + File = find_font_file_0(Table,FName, TryList), + ?DBG("~p => ~p~n", [FontInfo, File]), + {FontInfo, {FName, File}} + catch _:Er:St -> + io:format("~p: ~p~n",[Er,St]), + {FontInfo, undefined} + end. + +find_font_file_0(Table,FName,[{FStyle,FWeight}|Rest]) -> + case ets:lookup(Table, {FName,FStyle,FWeight}) of + [{_Key, FPath}] -> FPath; + [] -> find_font_file_0(Table,FName, Rest) + end; +find_font_file_0(Table,FName,[]) -> + case winregval("FontSubstitutes",FName) of + none -> undefined; + FSName -> + case ets:lookup(Table, {FSName,normal,normal}) of + [{_Key, FPath}] -> FPath; + [] -> undefined + end + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +find_tables(<<_:32, NumTables:?U16, _SR:16, _ES:16, _RS:16, Tables/binary>>) -> + find_table(NumTables, Tables, []). + +find_table(0, _, Tabs) -> maps:from_list(Tabs); +find_table(Num, <>, Tabs) -> + find_table(Num-1, Next, [{Tag, Offset}|Tabs]). + +num_glyphs(undefined, _Bin) -> + 16#ffff; +num_glyphs(Offset0, Bin) -> + Offset = Offset0+4, + <<_:Offset/binary, NG:?U16, ?SKIP>> = Bin, + NG. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +find_index_map(Cmap, Bin) -> + <<_:Cmap/binary, _:16, NumTables:?U16, Data/binary>> = Bin, + case find_index_map1(NumTables, Data, []) of + [] -> throw({errror, unsupported_format}); + Alternatives -> + [{_, Offset}|_] = lists:sort(Alternatives), + %% ?DBG("Index maps: ~p + ~p => ~p~n", [Cmap,lists:sort(Alternatives),Cmap + Offset]), + Cmap + Offset + end. + +find_index_map1(0, _, Res) -> Res; +find_index_map1(N, <>, Prev) -> + case Enc of + ?MS_EID_UNICODE_BMP -> + find_index_map1(N-1, Rest, [{5, Offset}|Prev]); + ?MS_EID_UNICODE_FULL -> + find_index_map1(N-1, Rest, [{1, Offset}|Prev]); + _ -> %% For example ?MS_EID_SYMBOL + find_index_map1(N-1, Rest, Prev) + end; +find_index_map1(N, <>, Prev) -> + case Enc of + 5 -> %% Cmap format 14 (we don't support that) + find_index_map1(N-1, Rest, Prev); + 4 -> + find_index_map1(N-1, Rest, [{0, Offset}|Prev]); + 3 -> + find_index_map1(N-1, Rest, [{4, Offset}|Prev]); + 6 -> + find_index_map1(N-1, Rest, [{2, Offset}|Prev]); + _ -> + find_index_map1(N-1, Rest, [{6, Offset}|Prev]) + end; +find_index_map1(NumTables, <<_:64, Next/binary>>, Res) -> + find_index_map1(NumTables-1, Next, Res). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Converts UnicodeCodePoint to Glyph index +%% Glyph 0 is the undefined glyph +-spec find_glyph_index(Font::ttf(), Char::integer()) -> Glyph::integer(). +find_glyph_index(#ttf_info{data=Bin, index_map=IndexMap, os2=_Os2}, UnicodeCP) -> + <<_:IndexMap/binary, Fmt:?U16, Data/binary>> = Bin, + %% ?DBG("Index map format: ~w @~w~n",[Fmt, IndexMap]), + + %% <<_:_Os2/binary,_Ver:16,_:16,_Weight:?U16,_Pad:26/binary, + %% _Panose:10/binary,R1:32,R2:32,R3:32,R4:32,?SKIP>> = Bin, + %% ?DBG("Support: ~.2b ~.2b ~.2b ~.2b~n",[R1, R2, R3, R4]), + + find_glyph_index(Fmt, Data, UnicodeCP). + +find_glyph_index(0, IndexMap, UnicodeCP) -> + <> = IndexMap, + %% Format0: Apple byte encoding + case UnicodeCP < (Bytes-6) of + true -> Index; + false -> ?DBG("No CP in range",[]), 0 + end; +find_glyph_index(4, IndexMap, UnicodeCP) -> + %% Format4: 16 bit mapping + <<_Len:16, _Lan:16, Format4/binary>> = IndexMap, + format_4_index(Format4, UnicodeCP); +find_glyph_index(6, IndexMap, UnicodeCP) -> + %% Format6: Dense 16 bit mapping + <<_Len:16, _Lang:16, First:?U16, Count:?U16, IndexArray/binary>> = IndexMap, + case UnicodeCP >= First andalso UnicodeCP < (First+Count) of + false -> ?DBG("No CP in index range",[]), 0; + true -> + Pos = (UnicodeCP - First)*2, + <<_:Pos/binary, Index:?U16, ?SKIP>> = IndexArray, + Index + end; +find_glyph_index(Format, IndexMap, UnicodeCP) + when Format =:= 12; Format =:= 13 -> + %% Format12/13: Mixed 16/32 and pure 32 bit mappings + <<_:16, _:32, _:32, Count:?U32, Groups/binary>> = IndexMap, + format_32_search(0, Count, Groups, UnicodeCP, Format); +find_glyph_index(_Format, _IndexMap, _UnicodeCP) -> + %% Format2: Mixed 8/16 bits mapping for Japanese, Chinese and Korean + %% Format8: Mixed 16/32 and pure 32 bit mappings + %% Format10: Mixed 16/32 and pure 32 bit mappings + ?DBG("unsupported glyph format ~w~n",[_Format]), + 0. + +format_4_index(_, Unicode) when Unicode >= 16#FFFF -> 0; +format_4_index(<>, Unicode) -> + %% SegCount = SegCountX2 div 2, + SearchRange = SearchRange0 div 2, + %% Binary Search + <> = Table, + + %% Ranges = lists:zip([Code || << Code:?U16 >> <= StartCode], + %% [Code || << Code:?U16 >> <= EndCode]), + %% Chars = [B-A+1 || {A,B} <- Ranges], + %% ?DBG("~w~n",[Ranges]), + %% ?DBG("~p ~w ~n",[lists:sum(Chars), Chars]), + + %% they lie from endCount .. endCount + segCount + %% but searchRange is the nearest power of two, so... + RangeShift = SegCountX2 - SearchRange0, + Search = case EndCode of + <<_:RangeShift/binary, Search0:?U16, ?SKIP>> + when Unicode >= Search0 -> + RangeShift; + _ -> 0 + end, + Item = format_4_search(EntrySel, Search-2, SearchRange, EndCode, Unicode), + case EndCode of + <<_:Item/binary, Assert:16, ?SKIP>> -> + true = Unicode =< Assert; + _ -> exit(assert) + end, + <<_:Item/binary, Start:?U16, ?SKIP>> = StartCode, + %% <<_:Item/binary, End:?U16, ?SKIP>> = EndCode, + <<_:Item/binary, Offset:?U16, ?SKIP>> = IdRangeOffset, + if + Unicode < Start -> + %% ?DBG("Unicode: ~w start ~w~n",[Unicode, Start]), + 0; + Offset =:= 0 -> + <<_:Item/binary, Index:?S16, ?SKIP>> = IdDelta, + (Index + Unicode) rem 65536; + true -> + Skip = Item + Offset + (Unicode - Start)*2, + <<_:Skip/binary, Index:?U16, ?SKIP>> = IdRangeOffset, + Index + end. + +format_4_search(EntrySel, Start, SearchRange, Bin, Unicode) when EntrySel > 0 -> + Index = Start + SearchRange, + case Bin of + <<_:Index/binary, End:?U16, ?SKIP>> when Unicode > End -> + format_4_search(EntrySel-1, Start+SearchRange, SearchRange div 2, Bin, Unicode); + _ -> + format_4_search(EntrySel-1, Start, SearchRange div 2, Bin, Unicode) + end; +format_4_search(_, Search, _, _, _) -> + Search+2. + +format_32_search(Low, High, Groups, UnicodeCP, Format) + when Low < High -> + Mid = Low + ((High - Low) div 2), + MidIndex = Mid*12, + <<_:MidIndex/binary, Start:?U32, End:?U32, Glyph:?U32, ?SKIP>> = Groups, + if + UnicodeCP < Start -> + format_32_search(Low, Mid, Groups, UnicodeCP, Format); + UnicodeCP > End -> + format_32_search(Mid+1, High, Groups, UnicodeCP, Format); + Format =:= 12 -> + Glyph+UnicodeCP-Start; + Format =:= 13 -> + Glyph + end; +format_32_search(_, _, _, _, _) -> 0. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_glyf_offset(#ttf_info{num_glyphs=NumGlyphs}, Glyph) + when Glyph > NumGlyphs -> + ?DBG("Out of range ~p max: ~p~n",[Glyph, NumGlyphs]), + -1; +get_glyf_offset(#ttf_info{index_to_loc_format=0, data=Bin, loca=Loca, glyf=Glyf}, Glyph) -> + Skip = Glyph*2, + <<_:Loca/binary, _:Skip/binary, G1:?U16, G2:?U16, ?SKIP>> = Bin, + case G1 == G2 of + true -> -1; + false -> Glyf + G1 * 2 + end; +get_glyf_offset(#ttf_info{index_to_loc_format=1, data=Bin, loca=Loca, glyf=Glyf}, Glyph) -> + Skip = Glyph*4, + <<_:Loca/binary, _:Skip/binary, G1:?U32, G2:?U32, ?SKIP>> = Bin, + case G1 == G2 of + true -> -1; %% Length is zero + false -> Glyf + G1 + end; +get_glyf_offset(#ttf_info{index_to_loc_format=_F}, _) -> + ?DBG("unknown glyph map format: ~p~n",[_F]), + -1. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% leftSideBearing is the offset from the current horizontal position +%% to the left edge of the character advanceWidth is the offset from +%% the current horizontal position to the next horizontal position +%% these are expressed in unscaled coordinates +-spec get_glyph_h_metrics(Font::ttf(), Glyph::integer()) -> + { Advance::integer(), + LeftSideBearing::integer()}. +get_glyph_h_metrics(#ttf_info{data=Bin, hhea=Hhea, hmtx=Hmtx}, Glyph) -> + <<_:Hhea/binary, _:34/binary, LongHorMetrics:?U16, ?SKIP>> = Bin, + case Glyph < LongHorMetrics of + true -> + Skip = 4*Glyph, + <<_:Hmtx/binary, _:Skip/binary, Advance:?S16, LeftSideBearing:?S16, ?SKIP>> = Bin, + {Advance, LeftSideBearing}; + false -> + Skip1 = 4*(LongHorMetrics-1), + <<_:Hmtx/binary, _:Skip1/binary, Advance:?S16, ?SKIP>> = Bin, + Skip2 = 4*LongHorMetrics+2*(Glyph-LongHorMetrics), + <<_:Hmtx/binary, _:Skip2/binary, LeftSideBearing:?S16, ?SKIP>> = Bin, + {Advance, LeftSideBearing} + end. + +%% Computes a scale factor to produce a font whose EM size is mapped to +%% 'pixels' tall. +-spec scale_for_mapping_em_to_pixels(Font::ttf(), Size::number()) -> Scale::float(). +scale_for_mapping_em_to_pixels(#ttf_info{data=Bin, head=Head}, Size) -> + <<_:Head/binary, _:18/binary, UnitsPerEm:?U16, ?SKIP>> = Bin, + Size / UnitsPerEm. + +%% -spec get_glyph_box(Font::ttf(), Glyph::integer()) -> +%% {X0::integer(),Y0::integer(), +%% X1::integer(),Y1::integer()}. +%% get_glyph_box(TTF = #ttf_info{data=Bin, glyf=Glyf}, Glyph) +%% when Glyf =/= undefined -> +%% case get_glyf_offset(TTF, Glyph) of +%% Offset when Offset > 0 -> +%% <<_:Offset/binary, _:16, X0:?S16, Y0:?S16, X1:?S16, Y1:?S16, ?SKIP>> = Bin, +%% {X0,Y0,X1,Y1}; +%% _ -> +%% {0,0,0,0} +%% end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec get_glyph_shape(Font::ttf(), Glyph::integer()) -> [Vertices::vertex()]. +get_glyph_shape(#ttf_info{glyf=undefined}=TTF, Glyph) -> + get_glyph_shape_tt2(TTF, Glyph); +get_glyph_shape(TTF, Glyph) -> + get_glyph_shape_tt(TTF, Glyph, get_glyf_offset(TTF, Glyph)). + +get_glyph_shape_tt(_TTF, _Glyph, Offset) + when Offset < 0 -> []; +get_glyph_shape_tt(TTF = #ttf_info{data=Bin}, _Glyph, Offset) -> + <<_:Offset/binary, NumberOfContours:?S16, + _XMin:16, _YMin:16, _XMax:16, _YMax:16, + GlyphDesc/binary>> = Bin, + + %% ?DBG("Glyph: ~p ~p c#~p ~p ~p~n",[_Glyph, Offset, NumberOfContours, {_XMin,_XMax}, {_YMin,_YMax}]), + + if NumberOfContours > 0 -> + %% Single Glyph + Skip = NumberOfContours*2 - 2, + <<_:Skip/binary, Last:?U16, InsLen:?U16, Instr/binary>> = GlyphDesc, + N = 1 + Last, + <<_:InsLen/binary, FlagsBin/binary>> = Instr, + %%io:format("Conts ~p ~p ~p~n",[NumberOfContours, InsLen, N]), + {Flags, XCoordsBin} = parse_flags(N, 0, FlagsBin, []), + {XCs, YCoordsBin} = parse_coords(Flags, XCoordsBin, 0, 2, []), + {YCs, _} = parse_coords(Flags, YCoordsBin, 0, 4, []), + N = length(Flags), + setup_vertices(Flags, XCs, YCs, GlyphDesc); + NumberOfContours =:= -1 -> + %% Several Glyphs (Compund shapes) + get_glyph_shapes(GlyphDesc, TTF, []); + NumberOfContours < -1 -> + throw({error, bad_ttf, ?__(1, "Unsupported TTF format")}); + NumberOfContours =:= 0 -> + [] + end. + +parse_flags(N, 0, <>, Flags) + when N > 0 -> + case (Flag band 8) > 1 of + false -> + parse_flags(N-1, 0, Rest, [Flag|Flags]); + true -> + <> = Rest, + parse_flags(N-1, Repeat, Next, [Flag|Flags]) + end; +parse_flags(N, R, Rest, Flags = [Prev|_]) + when N > 0 -> + parse_flags(N-1, R-1, Rest, [Prev|Flags]); +parse_flags(0, 0, Rest, Flags) -> {lists:reverse(Flags), Rest}. + +%% repeat(0, _, Flags) -> Flags; +%% repeat(N, Flag, Flags) -> repeat(N-1, Flag, [Flag|Flags]). + +parse_coords([Flag|Flags], <>, X0, Mask, Xs) + when (Flag band Mask) > 1, (Flag band (Mask*8)) > 1 -> + X = X0+DX, + parse_coords(Flags, Coords, X, Mask, [X|Xs]); +parse_coords([Flag|Flags], <>, X0, Mask, Xs) + when (Flag band Mask) > 1 -> + X = X0-DX, + parse_coords(Flags, Coords, X, Mask, [X|Xs]); +parse_coords([Flag|Flags], Coords, X, Mask, Xs) + when (Flag band (Mask*8)) > 1 -> + parse_coords(Flags, Coords, X, Mask, [X|Xs]); +parse_coords([_|Flags], <>, X0, Mask, Xs) -> + X = X0 + DX, + parse_coords(Flags, Coords, X, Mask, [X|Xs]); +parse_coords([], Rest, _, _, Xs) -> + {lists:reverse(Xs), Rest}. + +setup_vertices(Flags, XCs, YCs, GlyphDesc) -> + setup_vertices(Flags, XCs, YCs, GlyphDesc, 0, -1, {0,0}, false,false, []). + +setup_vertices([Flag|Fs0], [X|XCs0], [Y|YCs0], GD, StartC, Index, + S0, WasOff, StartOff0, Vs0) + when StartC < 2 -> + Vs1 = case StartC of + 0 -> Vs0; %% First + 1 -> close_shape(Vs0, S0, WasOff, StartOff0) + end, + %% Start new one + <> = GD, + Next = Next0-Index, + case (Flag band 1) =:= 0 of + true -> + StartOff = {X,Y}, %% Save for warparound + [FN|Fs1] = Fs0, + [XN|Xcs1] = XCs0, + [YN|Ycs1] = YCs0, + {S,Skip,Fs,XCs,YCs} = + case ((FN band 1) =:= 0) of + true -> %% Next is also off + {{(X+XN) div 2, (Y+YN) div 2},0, + Fs0, XCs0, YCs0}; + false -> + {{XN, YN},1,Fs1,Xcs1,Ycs1} + end, + %%io:format("SOff ~p ~p ~p~n",[(Flag band 1) =:= 0, S, Next]), + Vs = set_vertex(Vs1, move, S, {0,0}), + setup_vertices(Fs,XCs,YCs,NextGD,Next-Skip,Next0,S,false,StartOff,Vs); + false -> + S = {X,Y}, + %%io:format("Start ~p ~p ~p~n",[(Flag band 1) =:= 0, S, Next]), + Vs = set_vertex(Vs1, move, S, {0,0}), + setup_vertices(Fs0,XCs0,YCs0,NextGD,Next,Next0,S,false,false,Vs) + end; +setup_vertices([Flag|Fs], [X|XCs], [Y|YCs], GD, Next,Index,S,WasOff,StartOff,Vs0) -> + %%io:format("~p ~p~n",[(Flag band 1) =:= 0, WasOff /= false]), + case {(Flag band 1) =:= 0, WasOff} of + {true, {Cx,Cy}} -> + %% two off-curve control points in a row means interpolate an on-curve midpoint + Int = {(X+Cx) div 2, (Y+Cy) div 2}, + Vs = set_vertex(Vs0, curve, Int, WasOff), + setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,{X,Y}, StartOff, Vs); + {true, false} -> + setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,{X,Y}, StartOff, Vs0); + {false,false} -> + Vs = set_vertex(Vs0, line, {X,Y}, {0,0}), + setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,false, StartOff, Vs); + {false,C} -> + Vs = set_vertex(Vs0, curve, {X,Y}, C), + setup_vertices(Fs,XCs,YCs, GD, Next-1,Index,S,false, StartOff, Vs) + end; +setup_vertices([], [], [], _, _Next, _, S, WasOff, StartOff, Vs) -> + lists:reverse(close_shape(Vs, S, WasOff, StartOff)). + +close_shape(Vs0, S={SX,SY}, C={CX,CY}, SC={_SCX,_SCY}) -> + Vs1 = set_vertex(Vs0, curve, {(SX+CX) div 2, (SY+CY) div 2}, C), + set_vertex(Vs1, curve, S, SC); +close_shape(Vs, S, false, SC={_SCX,_SCY}) -> + set_vertex(Vs, curve, S, SC); +close_shape(Vs, S, C={_CX,_CY}, false) -> + set_vertex(Vs, curve, S, C); +close_shape(Vs, S, false, false) -> + set_vertex(Vs, line, S, {0,0}). + +set_vertex(Vs, Mode, Pos, C) -> + %% io:format(" ~p ~p ~p~n",[Mode, Pos, C]), + [#vertex{type=Mode, pos=Pos, c=C}|Vs]. +set_vertex(Vs, Mode, Pos, C, C1) -> + %% io:format(" ~p ~p ~p ~p~n", [Mode, Pos, C, C1]), + [#vertex{type=Mode, pos=Pos, c=C, c1=C1}|Vs]. + +get_glyph_shapes(<>, Font, Vs0) -> + {ScaleInfo,GlyphDesc} = find_trans_scales(Flags, GlyphDesc0), + Vs1 = get_glyph_shape(Font, GidX), + Vs = scale_vertices(Vs1, ScaleInfo, Vs0), + case (Flags band (1 bsl 5)) > 1 of + true -> %% More Compontents + get_glyph_shapes(GlyphDesc, Font, Vs); + false -> + lists:reverse(Vs) + end. + +find_trans_scales(Flags, + <>) + when (Flags band 3) > 2 -> + find_trans_scales(Flags, Mtx4, Mtx5, GlyphDesc); +find_trans_scales(Flags, <>) + when (Flags band 2) > 1 -> + find_trans_scales(Flags, Mtx4, Mtx5, GlyphDesc). +%% @TODO handle matching point +%%find_trans_scales(Flags, GlyphDesc0) -> + +find_trans_scales(Flags, Mtx4, Mtx5, <>) + when (Flags band (1 bsl 3)) > 1 -> + %% We have a scale + S = 1 / 16384, + {calc_trans_scales(Mtx0*S, 0, 0, Mtx0*S, Mtx4, Mtx5),GlyphDesc}; +find_trans_scales(Flags, Mtx4, Mtx5, <>) + when (Flags band (1 bsl 6)) > 1 -> + %% We have a X and Y scale + S = 1 / 16384, + {calc_trans_scales(Mtx0*S, 0, 0, Mtx3*S, Mtx4, Mtx5), GlyphDesc}; +find_trans_scales(Flags, Mtx4, Mtx5, + <>) + when (Flags band (1 bsl 7)) > 1 -> + %% We have a two by two + S = 1 / 16384, + {calc_trans_scales(Mtx0*S, Mtx1*S, Mtx2*S, Mtx3*S, Mtx4, Mtx5), GlyphDesc}; +find_trans_scales(_, Mtx4, Mtx5, GlyphDesc) -> + {calc_trans_scales(1.0, 0.0, 0.0, 1.0, Mtx4, Mtx5), GlyphDesc}. + +calc_trans_scales(Mtx0, Mtx1, Mtx2, Mtx3, Mtx4, Mtx5) -> + {math:sqrt(square(Mtx0)+square(Mtx1)), + math:sqrt(square(Mtx2)+square(Mtx3)), Mtx0, Mtx1, Mtx2, Mtx3, Mtx4, Mtx5}. + +scale_vertices([#vertex{pos={X,Y}, c={CX,CY}, type=Type}|Vs], + SI={M,N, Mtx0, Mtx1, Mtx2, Mtx3, Mtx4, Mtx5}, Acc) -> + V = #vertex{type=Type, + pos = {round(M*(Mtx0*X+Mtx2*Y+Mtx4)), + round(N*(Mtx1*X+Mtx3*Y+Mtx5))}, + c = {round(M*(Mtx0*CX+Mtx2*CY+Mtx4)), + round(N*(Mtx1*CX+Mtx3*CY+Mtx5))}}, + scale_vertices(Vs, SI, [V|Acc]); +scale_vertices([], _, Acc) -> Acc. + +square(X) -> X*X. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% CFF stuff + +pp_cff(undefined, _Bin, _File) -> + undefined; +pp_cff(CffIdx, Bin0, _DbgFontFile) -> + <<_:CffIdx/binary,Cff/binary>> = Bin0, + <<1, _MinorVsn, HeaderSz, ?SKIP>> = Cff, + <<_:HeaderSz/binary, Cont0/binary>> = Cff, + {_NameIdx, Cont1} = cff_index(Cont0), + {TopDictIdx, Cont2} = cff_index(Cont1), + TopDict = get_cff_index(0, TopDictIdx), %% OpenType only supports one + %?DBG("Strings: ~.16b~n",[byte_size(Cff) - byte_size(Cont2)]), + {_StringIdx, Cont3} = cff_index(Cont2), + %?DBG("Global Subrs: ~.16b~n",[byte_size(Cff) - byte_size(Cont3)]), + {Gsubrs, _Cont4} = cff_index(Cont3), + CharStringsOff = get_cff_dict(17,TopDict), + CsType = get_cff_dict({12,6},TopDict), + FdArrayOff = get_cff_dict({12,36},TopDict), + FdSelectOff = get_cff_dict({12,37},TopDict), + if CsType =:= 2; CsType =:= [], CharStringsOff /= [] -> ok; + true -> throw({error, bad_cff_cstype, ?__(1, "Not supported OTF (cff) format")}) + end, + + Fd = case FdArrayOff of + [] -> undefined; + _ when FdSelectOff =/= [] -> %% Looks like a CID font? + <<_:FdArrayOff/binary, FdArrBin/binary>> = Cff, + {FontDicts, _} = cff_index(FdArrBin), + %% ?DBG("~s: ~p ~p ~p~n",[_DbgFontFile, FdArrayOff, FdSelectOff, size(Cff)]), + <<_:FdSelectOff/binary, FdSelectBin/binary>> = Cff, + {FontDicts, FdSelectBin}; + _ -> + throw({error, no_fd_select, ?__(1, "Not supported OTF (cff) format")}) + end, + Subrs = cff_get_subrs(TopDict, Cff), + %% ?DBG("ChStr ~p type ~p FdA ~p FdS ~p ~p~n", + %% [CharStringsOff, CsType, FdArrayOff, FdSelectOff, size(Cff)]), + <<_:CharStringsOff/binary, CharSBin/binary>> = Cff, + {CharStrings, _Rest} = cff_index(CharSBin), + #{topDict => TopDict, + gsubrs => Gsubrs, + subrs => Subrs, + charStrings => CharStrings, + fdSel => Fd, + cff => Cff + }. + +cff_get_subrs(Dict, Bin) -> + case get_cff_dict(18, Dict) of + [Size, Offset] when Size /= 0, Offset /= 0 -> + %% ?DBG("~p ~p ~.16b~n",[Size, Offset, Offset]), + <<_:Offset/binary, PrivDict:Size/binary, _Rest/binary>> = Bin, + case get_cff_dict(19, PrivDict) of + [] -> undefined; + SubrsOffs0 -> + SubrsOffs = SubrsOffs0 + Offset, + <<_:SubrsOffs/binary, Subrs/binary>> = Bin, + {Index, _} = cff_index(Subrs), + Index + end; + _ -> + undefined + end. + +cff_index(<<0:16, Rest0/binary>>) -> + {undefined, Rest0}; +cff_index(<>) when 1 =< OffSz, OffSz =< 4 -> + TabSz = OffSz*(Count+1), Bits = OffSz*8, + %% io:format("~p ~p ~p ~p~n", [Count, OffSz, TabSz, Bits]), + <> = Rest0, + Offsets = [OS-1 || << OS:Bits >> <= Offsets0], + Last = lists:last(Offsets), + %% io:format("~p ~p ~p ~p ~p~n", [Count, OffSz, TabSz, Bits, Last]), + <<_:Last/binary, Rest/binary>> = Rest1, + {{array:from_list(Offsets), Rest1}, Rest}. + +get_cff_index(Idx, {Offsets, Bin}) -> + Offset = array:get(Idx, Offsets), + Sz = array:get(Idx+1, Offsets) - Offset, + <<_:Offset/binary, Data:Sz/binary, ?SKIP>> = Bin, + Data. + +get_cff_index_count({Offsets, _Bin}) -> + array:size(Offsets). + +get_cff_dict(Key, Bin) -> + get_ccf_dict(Bin, [], Key). + +get_ccf_dict(<>=Bin, Vals, Key) + when Data >= 28 -> + {Val, Rest} = ccf_dict_operand(Bin), + get_ccf_dict(Rest, [Val|Vals], Key); +get_ccf_dict(<>, Val, Key) -> + dict_val(Val); +get_ccf_dict(<<12, Op, Rest/binary>>, Val, Key) -> + case Key of + {12,Op} -> dict_val(Val); + _ -> get_ccf_dict(Rest, [], Key) + end; +get_ccf_dict(<<_Key, Rest/binary>>, _Val, Key) -> + %% io:format("Not found ~p: ~p~n",[_Key,_Val]), + get_ccf_dict(Rest, [], Key); +get_ccf_dict(<<>>, [], _) -> + []. + +dict_val([{float, _Bin}]) -> + throw({nyi, cff_float}); +dict_val([Val]) -> + Val; +dict_val(Vals) when is_list(Vals) -> + lists:reverse(Vals). + +ccf_dict_operand(<<28, Val:?S16, Rest/binary>>) -> + {Val,Rest}; +ccf_dict_operand(<<29, Val:?S32, Rest/binary>>) -> + {Val,Rest}; +ccf_dict_operand(<<30, Bin/binary>>) -> + Rest = ccf_dict_skip_float(Bin), + %% Sz = byte_size(Bin) - byte_size(Rest), + %% <> = Rest, + {{float, aFloatBin}, Rest}; +ccf_dict_operand(<>) + when 31 < B, B < 247 -> + {B-139, Rest}; +ccf_dict_operand(<>) + when 31 < B0, B0 < 255 -> + case B0 < 251 of + true -> {(B0-247)*256+B1+108, Rest}; + false -> {-(B0-251)*256-B1-108, Rest} + end. + +ccf_dict_skip_float(<<16#F:4, _:4, Rest/binary>>) -> Rest; +ccf_dict_skip_float(<<_:4, 16#F:4, Rest/binary>>) -> Rest; +ccf_dict_skip_float(<<_, Rest/binary>>) -> + ccf_dict_skip_float(Rest). + +get_glyph_subrs(Glyph, {FontDicts, <>}, Cff) -> + FdSel = case Fmt of + 0 -> %% Untested !!! + <<_:Glyph/binary, FdSelByte, ?SKIP>> = FdSelBin, + FdSelByte; + 3 -> + <> = FdSelBin, + get_glyph_fd(NRanges, Start, Glyph, Cont0) + end, + %% ?DBG("Subr Format ~p sel: ~p index: ~p~n",[Fmt, FdSel, get_cff_index(FdSel, FontDicts)]), + cff_get_subrs(get_cff_index(FdSel, FontDicts), Cff). + +get_glyph_fd(N, Start, Glyph, Bin) when N > 0 -> + <> = Bin, + case Start =< Glyph andalso Glyph < End of + true -> V; + false -> get_glyph_fd(N-1, End, Glyph, Cont) + end; +get_glyph_fd(_, _, _, _) -> + throw({error, fd_not_found, "Not supported"}). + +get_glyph_shape_tt2(#ttf_info{cff=CFF} = _TTF, Glyph) -> + run_charstring(CFF, Glyph). + +run_charstring(#{charStrings := CharSs}=Cff, Glyph) -> + Ops = get_cff_index(Glyph, CharSs), + State = Cff#{ %% Add the following temporary variables + in_header => true, + maskbits => 0, + has_subrs => false, + glyph => Glyph + }, + %% ?DBG("Glyph: ~w ~P~n",[Glyph, Ops, 40]), + {return, {_,_,All}} = run_chars(Ops, [], State, csctx_new()), + lists:reverse(All). + +run_chars(Bin, Stack, State, Acc) -> + %% ?DBG("~.16b (~w) ~w~n", [binary:first(Bin), length(Stack), Stack]), + do_run_chars(Bin, Stack, State, Acc). + +do_run_chars(<<16#13, Rest0/binary>>, Stack, #{maskbits:=MB0, in_header:=InH}=State, Acc)-> + %% Hintmask NYI + MB = case InH of + true -> (length(Stack) div 2) + MB0; + false -> MB0 + end, + Skip = (MB + 7) div 8, + <<_:Skip/binary, Rest/binary>> = Rest0, + run_chars(Rest, [], State#{maskbits:=MB,in_header:=false}, Acc); +do_run_chars(<<16#14, Rest0/binary>>, Stack, #{maskbits:=MB0, in_header:=InH}=State, Acc) -> + %% CNTRMASK + MB = case InH of + true -> (length(Stack) div 2) + MB0; + false -> MB0 + end, + Skip = (MB + 7) div 8, + <<_:Skip/binary, Rest/binary>> = Rest0, + run_chars(Rest, [], State#{maskbits:=MB,in_header=>false}, Acc); +do_run_chars(<>, Stack, #{maskbits:=MB0}=State, Acc) + when Stem =:= 16#01; %% hstem + Stem =:= 16#03; %% vstem + Stem =:= 16#12; %% hstemhm + Stem =:= 16#17 -> %% vstemhm + MB = (length(Stack) div 2) + MB0, + run_chars(Rest, [], State#{maskbits:=MB}, Acc); + +do_run_chars(<<16#15, Rest/binary>>, [S2,S1|_], State, Acc0) -> + %% rmoveto + Acc = csctx_rmove_to(S1,S2,Acc0), + run_chars(Rest, [], State#{in_header:=false}, Acc); +do_run_chars(<<16#04,Rest/binary>>, [S1|_], State, Acc0) -> + %% vmoveto + Acc = csctx_rmove_to(0, S1, Acc0), + run_chars(Rest, [], State#{in_header:=false}, Acc); +do_run_chars(<<16#16,Rest/binary>>, [S1|_], State, Acc0) -> + %% hmoveto + Acc = csctx_rmove_to(S1, 0, Acc0), + run_chars(Rest, [], State#{in_header:=false}, Acc); + +do_run_chars(<<16#05,Rest/binary>>, Stack, State, Acc0) -> + Acc = rlineto(reverse(Stack), Acc0), + run_chars(Rest, [], State#{in_header:=false}, Acc); + +do_run_chars(<<16#06,Rest/binary>>, Stack, State, Acc0) -> + Acc = hlineto(reverse(Stack), Acc0), + run_chars(Rest, [], State#{in_header:=false}, Acc); +do_run_chars(<<16#07,Rest/binary>>, Stack, State, Acc0) -> + Acc = vlineto(reverse(Stack), Acc0), + run_chars(Rest, [], State#{in_header:=false}, Acc); + +do_run_chars(<<16#1E,Rest/binary>>, Stack, State, Acc0) -> + Acc = vhcurveto(reverse(Stack), Acc0), + run_chars(Rest, [], State, Acc); +do_run_chars(<<16#1F,Rest/binary>>, Stack, State, Acc0) -> + Acc = hvcurveto(reverse(Stack), Acc0), + run_chars(Rest, [], State, Acc); +do_run_chars(<<16#08,Rest/binary>>, Stack, State, Acc0) -> + Acc = rrcurveto(reverse(Stack), Acc0), + run_chars(Rest, [], State, Acc); + +do_run_chars(<<16#18,Rest/binary>>, Stack, State, Acc0) -> + Acc = rrcurveline(reverse(Stack), Acc0), + run_chars(Rest, [], State, Acc); +do_run_chars(<<16#19,Rest/binary>>, Stack, State, Acc0) -> + Acc = rrlinecurve(reverse(Stack), Acc0), + run_chars(Rest, [], State, Acc); + +do_run_chars(<<16#1A,Rest/binary>>, Stack0, State, Acc0) -> + [F|Stack1] = Stack = reverse(Stack0), + Acc = case length(Stack) rem 2 of + 1 -> vvcurveto(Stack1, F, Acc0); + 0 -> vvcurveto(Stack, 0.0, Acc0) + end, + run_chars(Rest, [], State, Acc); +do_run_chars(<<16#1B,Rest/binary>>, Stack0, State, Acc0) -> + [F|Stack1] = Stack = reverse(Stack0), + Acc = case length(Stack) rem 2 of + 1 -> hhcurveto(Stack1, F, Acc0); + 0 -> hhcurveto(Stack, 0.0, Acc0) + end, + run_chars(Rest, [], State, Acc); + +do_run_chars(<<16#0A,Rest/binary>>, [V|Stack0], State0, Acc0) -> + {State1,Subrs} = + case maps:get(has_subrs, State0) orelse maps:get(fdSel,State0, undefined) of + true -> {State0, maps:get(subrs, State0)}; + undefined -> {State0, maps:get(subrs, State0)}; + FdSel -> + #{cff := Cff, glyph := Glyph} = State0, + GlSubrs = get_glyph_subrs(Glyph, FdSel, Cff), + {State0#{has_subrs := true, subrs := GlSubrs}, GlSubrs} + end, + %%?DBG("Recurse ...~p~n",[Stack0]), + case callsubr(V, Subrs, Stack0, State1, Acc0) of + {subrr, Stack, State, Acc} -> + %%?DBG("...done ~p~n",[Stack]), + run_chars(Rest, Stack, State, Acc); + {return, _} = Acc -> + %%?DBG("...done return~n",[]), + <<>> = Rest, %% Assert + Acc + end; +do_run_chars(<<16#1D,Rest/binary>>, [V|Stack0], #{gsubrs := GSubrs} = State0, Acc0) -> + %%?DBG("Recurse ...~p~n",[Stack0]), + case callsubr(V, GSubrs, Stack0, State0, Acc0) of + {subrr, Stack, State, Acc} -> + %%?DBG("...done ~p~n",[Stack]), + run_chars(Rest, Stack, State, Acc); + {return, _} = Acc -> + %%?DBG("...done return~n",[]), + <<>> = Rest, %% Assert + Acc + end; + +do_run_chars(<<16#0B,Rest/binary>>, Stack, State, Acc) -> + %% Return (subr) + <<>> = Rest, + {subrr, Stack, State, Acc}; +do_run_chars(<<16#0E,Rest/binary>>, _, _State, Acc) -> + %% endchar + <<>> = Rest, + {return, csctx_close_shape(Acc)}; + +do_run_chars(<<16#0C,B1,Rest/binary>>, Stack, State, Acc0) -> + %% Two-byte Escape-seq + Acc = run_char(B1,Stack,Acc0), + run_chars(Rest, [], State, Acc); +do_run_chars(<<16#FF, Int:32, Rest/binary>>, Stack, State, Acc) -> + run_chars(Rest, [Int/16#10000|Stack], State, Acc); +do_run_chars(Bin, Stack, State, Acc) -> + try ccf_dict_operand(Bin) of + {Val, Rest} -> + run_chars(Rest, [Val|Stack], State, Acc) + catch _:_ -> + <> = Bin, + io:format("Bad op: 16#~.16b ~p~n ~P~n ~P~nin ~P~n", + [Byte, Stack, State, 10, Acc, 20, Bin, 40]), + throw({error, parse_error}) + end. + +run_char(16#22, [Dx6,Dx5,Dx4,Dx3,Dy2,Dx2,Dx1], Acc0) -> + %% hflex + Acc = csctx_rccurve_to(Dx1,0,Dx2,Dy2,Dx3,0,Acc0), + csctx_rccurve_to(Dx4,0,Dx5,-Dy2,Dx6,0,Acc); +run_char(16#23, [_Fd, Dy6,Dx6,Dy5,Dx5,Dy4,Dx4,Dy3,Dx3,Dy2,Dx2,Dy1,Dx1], Acc0) -> + %% flex + Acc = csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,Dy3,Acc0), + csctx_rccurve_to(Dx4,Dy4,Dx5,Dy5,Dx6,Dy6,Acc); +run_char(16#24, [Dx6,Dy5,Dx5,Dx4,Dx3,Dy2,Dx2,Dy1,Dx1], Acc0) -> + %% hflex1 + Acc = csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,0,Acc0), + csctx_rccurve_to(Dx4,0,Dx5,Dy5,Dx6,-(Dy1+Dy2+Dy5),Acc); +run_char(16#25, [D6,Dy5,Dx5,Dy4,Dx4,Dy3,Dx3,Dy2,Dx2,Dy1,Dx1], Acc0) -> + %% flex1 + Dx = Dx1+Dx2+Dx3+Dx4+Dx5, + Dy = Dy1+Dy2+Dy3+Dy4+Dy5, + {Dx6,Dy6} = case abs(Dx) > abs(Dy) of + true -> {D6, -Dy}; + false -> {-Dx, D6} + end, + Acc = csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,Dy3,Acc0), + csctx_rccurve_to(Dx4,Dy4,Dx5,Dy5,Dx6,Dy6,Acc). + +callsubr(N0, Subrs, Stack, State, Acc) -> + Count = get_cff_index_count(Subrs), + Bias = if Count >= 33900 -> 32768; + Count >= 1240 -> 1131; + true -> 107 + end, + N = N0+Bias, + ((N < 0) orelse (N >= Count)) andalso + throw({error, internal_error, "A bug is found"}), + Bin = get_cff_index(N, Subrs), + %% ?DBG("sub ~W~n",[Bin, 40]), + run_chars(Bin, Stack, State, Acc). + +rlineto([S0,S1|St], Acc) -> + rlineto(St, csctx_rline_to(S0,S1,Acc)); +rlineto([], Acc) -> Acc. + +%% Note: hlineto/vlineto alternate horizontal and vertical +%% starting from a different place. +hlineto([S0|St], Acc0) -> + vlineto(St, csctx_rline_to(S0, 0, Acc0)); +hlineto([], Acc) -> Acc. + +vlineto([S0|St], Acc0) -> + hlineto(St, csctx_rline_to(0, S0, Acc0)); +vlineto([], Acc) -> Acc. + +%% Note: vhcurveto/hvcurveto alternate horizontal and vertical +%% starting from a different place. +vhcurveto([S0,S1,S2,S3|St], Acc0) -> + S4 = case St of + [Last]-> Last; + _ -> 0.0 + end, + Acc = csctx_rccurve_to(0, S0, S1, S2, S3, S4, Acc0), + hvcurveto(St, Acc); +vhcurveto(_, Acc) -> Acc. + +hvcurveto([S0,S1,S2,S3|St], Acc0) -> + S4 = case St of + [Last] -> Last; + _ -> 0.0 + end, + Acc = csctx_rccurve_to(S0, 0, S1, S2, S4, S3, Acc0), + vhcurveto(St, Acc); +hvcurveto(_, Acc) -> Acc. + +rrcurveto([S0,S1,S2,S3,S4,S5|St], Acc0) -> + Acc = csctx_rccurve_to(S0,S1,S2,S3,S4,S5,Acc0), + rrcurveto(St,Acc); +rrcurveto(_, Acc) -> Acc. + +rrcurveline([S0,S1,S2,S3,S4,S5|St], Acc0) -> + Acc = csctx_rccurve_to(S0,S1,S2,S3,S4,S5,Acc0), + rrcurveline(St, Acc); +rrcurveline([S0,S1], Acc0) -> + csctx_rline_to(S0, S1, Acc0). + +rrlinecurve([S0,S1|St], Acc0) + when length(St) >= 6 -> + Acc = csctx_rline_to(S0, S1, Acc0), + rrlinecurve(St, Acc); +rrlinecurve([S0,S1,S2,S3,S4,S5], Acc0) -> + csctx_rccurve_to(S0,S1,S2,S3,S4,S5,Acc0). + +vvcurveto([S0,S1,S2,S3|St], F, Acc0) -> + Acc = csctx_rccurve_to(F,S0,S1,S2,0.0,S3,Acc0), + vvcurveto(St, 0.0, Acc); +vvcurveto([], _, Acc) -> Acc. + +hhcurveto([S0,S1,S2,S3|St], F, Acc0) -> + Acc = csctx_rccurve_to(S0,F,S1,S2,S3,0.0,Acc0), + hhcurveto(St, 0.0, Acc); +hhcurveto([], _, Acc) -> Acc. + +csctx_new() -> + {{0.0,0.0},{0.0,0.0},[]}. + +csctx_close_shape({{Fx,Fy}=First, {X,Y}=XY, Shapes} = Acc) -> + case Fx /= X orelse Fy /= Y of + true -> + {First, XY, set_vertex(Shapes, line, First, {0,0})}; + false -> + Acc + end. + +csctx_rmove_to(Dx,Dy,Acc0) -> + {_First, {X,Y}, Shs} = csctx_close_shape(Acc0), + XY = {X+Dx,Y+Dy}, + {XY, XY, set_vertex(Shs, move, XY, {0,0})}. + +csctx_rline_to(Dx,Dy,{First,{X,Y},Shs}) -> + XY = {X+Dx,Y+Dy}, + {First, XY, set_vertex(Shs, line, XY, {0,0})}. + +csctx_rccurve_to(Dx1,Dy1,Dx2,Dy2,Dx3,Dy3,{First,{X,Y},Shs}) -> + Cx1 = X + Dx1, + Cy1 = Y + Dy1, + Cx2 = Cx1 + Dx2, + Cy2 = Cy1 + Dy2, + XY = {Cx2 + Dx3,Cy2 + Dy3}, + {First, XY, set_vertex(Shs, cubic, XY, {Cx1,Cy1}, {Cx2,Cy2})}. + + diff -Nru wings3d-2.2.4/README-22.txt wings3d-2.2.5/README-22.txt --- wings3d-2.2.4/README-22.txt 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/README-22.txt 2019-12-07 15:15:57.000000000 +0000 @@ -1,3 +1,41 @@ +--- 2.2.5 -------------------------------------------- + +- Added wings_convert script for batch conversion of models. [dgud] + +- Linux installer now accept an optional Install-Dir argument [dgud] + +- When exporting ".gltf/.glb" swap_y_z and scale options was ignored. [dgud] + +- Fixed importing non-square 'dds' textures with mipmaps + which caused an eternal loop. Thanks Simon Griffiths. [dgud] + +- Fixed crash when trying to edit area ligth properties + and not selecting an area ligth. Thanks Lars Thorsen. [dgud] + +- Fix ttf text plugin error handling. Thanks Hank. [dgud] + +- Add more search dirs for fonts. [dgud] + +- Rewrote "Text" primitive, it can now handle more truetype + formats, collection files (.ttc) and opentype (.otf) files. [dgud] + +- Fix lost text focus in dialogs. [dgud] + +- Allow image drag'n'drop from outliner to autouv to change + background image in autouv. [dgud] + +- Fixed updating mipmaps when updating dds files. Thanks Vershner. [dgud] + +- Fixed rendering bug when editing materials with vertex colors. Thanks Hank. [dgud] + +- The option "File->Save Selected" was saving images not used by the + selected object(s); Thanks Hank [Micheus Vieira] + +- Added an exporter to OpenJSCAD file format (.jscad); [micheus] + +- Added a minimal warp mouse camera workaround see 'Options/Misc/' tickbox. + Enable it and test again if you have problems. + --- 2.2.4 -------------------------------------------- - Fixed View/Show Texture bug. [dgud] diff -Nru wings3d-2.2.4/src/Makefile wings3d-2.2.5/src/Makefile --- wings3d-2.2.4/src/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -15,11 +15,11 @@ .fig .dvi .tex .class .java .pdf .psframe .pscrop include ../vsn.mk +include ../erl.mk ESRC=. WINGS_INTL=../intl_tools EBIN=../ebin -ERLC=erlc ifeq ($(TYPE),debug) TYPE_FLAGS=-DDEBUG +debug_info @@ -88,6 +88,7 @@ wings_palette \ wings_pb \ wings_pick \ + wings_pick_nif \ wings_plugin \ wings_pref \ wings_pref_dlg \ @@ -155,7 +156,7 @@ common: $(EBIN)/wings_lang.beam $(TARGET_FILES) $(APP_TARGET) template: $(TARGET_FILES) - erl -pa $(WINGS_INTL) -noinput -run tools generate_template ../ebin -run erlang halt + $(ERL) -pa $(WINGS_INTL) -noinput -run tools generate_template ../ebin -run erlang halt lang: template @cp *.lang $(EBIN) diff -Nru wings3d-2.2.4/src/wings_body.erl wings3d-2.2.5/src/wings_body.erl --- wings3d-2.2.4/src/wings_body.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_body.erl 2019-12-07 15:15:57.000000000 +0000 @@ -158,7 +158,7 @@ vertex_color_item(_) -> []. arealight_edit(Id, T) -> - [{?STR_LIGHT(menu,16,"Edit Properties..."),{edit_arealight,Id}, + [{?STR_LIGHT(menu,16,"Edit Area Light..."),{edit_arealight,Id}, ?STR_LIGHT(menu,17,"Edit light properties")}|T]. arealight_conv(arealight, T) -> diff -Nru wings3d-2.2.4/src/wings_camera.erl wings3d-2.2.5/src/wings_camera.erl --- wings3d-2.2.4/src/wings_camera.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_camera.erl 2019-12-07 15:15:57.000000000 +0000 @@ -29,7 +29,8 @@ -record(camera, {x,y, %Current mouse position. ox,oy, %Original mouse position. - xt=0,yt=0 %Last warp length. + xt=0,yt=0, %Last warp length. + w,h %Win size }). -record(state, {st, func}). @@ -208,7 +209,7 @@ %%% tweak_camera_event(Sym, X, Y, St) when Sym =:= $c; Sym =:= $s; Sym =:= $d -> - Camera = #camera{x=X,y=Y,ox=X,oy=Y}, + Camera = start_camera(X,Y), {seq,push,get_tweak_cam_event(Sym, Camera, St)}; tweak_camera_event(Sym, _, _, _) -> arrow_key_pan(Sym). @@ -263,7 +264,7 @@ blender(#mousebutton{button=2,state=?SDL_PRESSED,x=X,y=Y,mod=Mod}, Redraw) when Mod band ?ALT_BITS =:= 0 -> - Camera = #camera{x=X,y=Y,ox=X,oy=Y}, + Camera = start_camera(X,Y), grab(), message(blender_help()), {seq,push,get_blender_event(Camera, Redraw)}; @@ -305,7 +306,7 @@ nendo(#mousebutton{button=2,x=X,y=Y,mod=Mod,state=?SDL_RELEASED}, Redraw) when Mod band ?CTRL_BITS =:= 0 -> - Camera = #camera{x=X,y=Y,ox=X,oy=Y}, + Camera = start_camera(X,Y), grab(), MoveTumbles = allow_rotation(), nendo_message(MoveTumbles), @@ -374,7 +375,7 @@ mirai(#mousebutton{button=2,x=X,y=Y,mod=Mod,state=?SDL_RELEASED}, Redraw) when Mod band ?CTRL_BITS =:= 0 -> - Camera = #camera{x=X,y=Y,ox=X,oy=Y}, + Camera = start_camera(X,Y), grab(), MoveTumbles = allow_rotation(), mirai_message(MoveTumbles), @@ -644,7 +645,7 @@ wings_cam(#mousebutton{button=2,x=X,y=Y,mod=Mod,state=?SDL_RELEASED}, Redraw) when Mod band (?CTRL_BITS bor ?SHIFT_BITS bor ?ALT_BITS) =:= 0 -> - Camera = #camera{x=X,y=Y,ox=X,oy=Y}, + Camera = start_camera(X,Y), grab(), wings_cam_message(), View = wings_view:current(), @@ -769,7 +770,6 @@ zoom_step(Dir) end. - rotate(Dx, Dy) -> Speed = wings_pref:get_value(cam_rotation_speed,25)/25, case allow_rotation() of @@ -881,6 +881,10 @@ dist_factor(Dist) -> max(abs(Dist), 0.2). +start_camera(X,Y) -> + {0,0,W,H} = wings_wm:viewport(), + #camera{x=X,y=Y,ox=X,oy=Y,w=W,h=H}. + stop_camera(#camera{ox=Ox,oy=Oy}) -> wings_wm:release_focus(), case wings_io:ungrab(Ox, Oy) of @@ -893,17 +897,39 @@ pop. camera_mouse_range(X1, Y1, #camera{x=OX,y=OY, xt=Xt0, yt=Yt0}=Camera) -> -%% io:format("Camera Mouse Range ~p ~p~n", [{X0,Y0}, {OX,OY,Xt0,Yt0}]), + %% ?dbg("Camera ~w ~w~n", [X1,Y1]), XD0 = (X1 - OX), YD0 = (Y1 - OY), {XD,YD} = wings_pref:lowpass(XD0 + Xt0, YD0 + Yt0), - if XD0 =:= 0, YD0 =:= 0 -> - {0.0,0.0,Camera#camera{xt=0,yt=0}}; + case wings_pref:get_value(no_warp, false) of + false -> + {0.0,0.0,Camera#camera{xt=0,yt=0}}; + _ -> + #camera{w=W, h=H} = Camera, + Cx = W div 2, Cy = H div 2, + wings_io:warp(Cx, Cy), + {0.0,0.0,Camera#camera{xt=0,yt=0,x=Cx,y=Cy}} + end; true -> - wings_io:warp(OX, OY), - {XD/?CAMDIV, YD/?CAMDIV, Camera#camera{xt=XD0, yt=YD0}} + case wings_pref:get_value(no_warp, false) of + false -> + wings_io:warp(OX, OY), + {XD/?CAMDIV, YD/?CAMDIV, Camera#camera{xt=XD0, yt=YD0}}; + true -> + %% Warp as few times as possible + #camera{w=W, h=H} = Camera, + case X1 < 10 orelse Y1 < 10 orelse X1 > (W-10) orelse Y1 > (H-10) of + true -> + #camera{w=W, h=H} = Camera, + Cx = W div 2, Cy = H div 2, + wings_io:warp(Cx, Cy), + {XD/?CAMDIV, YD/?CAMDIV, Camera#camera{xt=XD0,yt=YD0,x=Cx,y=Cy}}; + false -> + {XD/?CAMDIV, YD/?CAMDIV, Camera#camera{xt=XD0,yt=YD0,x=X1,y=Y1}} + end + end end. view_hotkey(Ev, Camera, #state{st=St}) -> diff -Nru wings3d-2.2.4/src/wings_console.erl wings3d-2.2.5/src/wings_console.erl --- wings3d-2.2.4/src/wings_console.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_console.erl 2019-12-07 15:15:57.000000000 +0000 @@ -466,7 +466,7 @@ {State,{error,badarg}}. wc_open_window(#state{lines=Lines}=State, Win, Font) -> - TStyle = ?wxTE_MULTILINE bor ?wxTE_READONLY bor ?wxTE_RICH2, + TStyle = ?wxTE_MULTILINE bor ?wxTE_READONLY bor ?wxTE_RICH2 bor wings_frame:get_border(), Ctrl = wxTextCtrl:new(Win, ?wxID_ANY, [{style, TStyle}]), wxWindow:setFont(Ctrl, Font), diff -Nru wings3d-2.2.4/src/wings_convert wings3d-2.2.5/src/wings_convert --- wings3d-2.2.4/src/wings_convert 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_convert 1970-01-01 00:00:00.000000000 +0000 @@ -1,340 +0,0 @@ -#!/usr/bin/env escript -%% -*- erlang -*- -%% Wings 3D File convertion. -%% -%% Copyright (c) 2010 Dan Gudmundsson -%% -%% See the file "license.terms" for information on usage and redistribution -%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. -%% - --mode(compile). - -%% If moved outside of wings directory modify --define(WINGS_DIR, "c:/src/wings/ebin"). - --record(opts, - {dir = ".", %% Ouput to directory - out_module, %% Output format - verbose=false, %% Verbose output - in_format, %% In format (if unknown extension). - image_format, %% Image out format - in_formats=[], %% Scanned, all import formats - out_formats=[], %% Scanned, all export formats - modify=[] %% Convertion modifications - }). - --record(format, - {mod, %% Module - ext_type, %% Extension - str="", %% Description string - option=false %% Allows options - }). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -main(Args) -> - Wings_dir = setup_paths(), - ok = wings:start(), - Init = fun() -> - wxTopLevelWindow:iconize(wings_wm:get_value(top_frame)), - keep - end, - wings ! {action, Init}, - IEDir = filename:join(Wings_dir, "plugins/import_export"), - code:add_patha(IEDir), - Opts0 = scan_format([IEDir]), - put(verbose, false), - case parse_args(Args, Opts0) of - {#opts{out_module=undefined},_} -> - io:format("**** Error: Out format not specified~n~n"), - usage(Opts0); - {Opts = #opts{}, Files} -> - convert(Files, Opts), - erlang:halt(0); - error -> - usage(Opts0) - end. - -setup_paths() -> - Escript = filename:dirname(filename:absname(escript:script_name())), - EnvDir = os:getenv("WINGS_DIR"), - DefDir = ?WINGS_DIR, - case test_paths([Escript, EnvDir,DefDir]) of - {ok, Path} -> - code:add_patha(filename:join([Path, "ebin"])), - Path; - _ -> - io:format("**** Error: Compiled wings files not found~n~n"), - io:format(" use 'set WINGS_DIR=c:\PATH_TO_WINGS_INSTALL~n~n") - end. - -test_paths([false|Rest]) -> test_paths(Rest); -test_paths([Path0|Rest]) -> - Path = strip_path(lists:reverse(Path0)), - case filelib:is_regular(filename:join([Path, "ebin", "wings.beam"])) of - true -> {ok, Path}; - false -> test_paths(Rest) - end; -test_paths([]) -> not_found. - -strip_path("nibe/" ++ Path) -> lists:reverse(Path); -strip_path("crs/" ++ Path) -> lists:reverse(Path); -strip_path(Path) -> lists:reverse(Path). - -convert(Fs, Opts) -> - Convert = fun(File) -> - call_wings({file, confirmed_new}), - ok = import_file(File, Opts), - ok = do_mods(Opts), - Out = filename:rootname(filename:basename(File)), - export_file(Out, Opts) - end, - [Convert(File) || File <- Fs], - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -scan_format(Dir) -> - Files = filelib:wildcard(filename:join(Dir, "wpc_*.beam")), - Plugin = fun(File, {Type, Acc}) -> - Mod = list_to_atom(filename:rootname(filename:basename(File))), - case Mod:menu({file, Type}, []) of - [{Str, ExtT, Extra}] -> - F = #format{mod=Mod, ext_type=ExtT, str = strip(Str), - option = lists:member(option, Extra) - }, - {Type,[F|Acc]}; - [{Str, ExtT}] -> - F = #format{mod=Mod, ext_type=ExtT, str = strip(Str)}, - {Type,[F|Acc]}; - [] -> - {Type, Acc} - end - end, - Default = [#format{mod=nendo, ext_type=ndo, str="Nendo (.ndo)"}, - #format{mod=wings, ext_type=wings, str="Wings (.wings)"}], - {_,Export} = lists:foldl(Plugin, {export, Default}, Files), - {_,Import} = lists:foldl(Plugin, {import, Default}, Files), - - #opts{in_formats=Import, out_formats=Export}. - -strip(Str) -> - strip_1(lists:reverse(Str)). - -strip_1([$.|Rest]) -> - strip_1(Rest); -strip_1(Str) -> - lists:reverse(Str). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -parse_args(["-o", Dir|Rest], Opts) -> - parse_args(Rest, Opts#opts{dir=Dir}); -parse_args(["--outdir", Dir|Rest], Opts) -> - parse_args(Rest, Opts#opts{dir=Dir}); -parse_args(["-v"|Rest], Opts) -> - put(verbose, true), - parse_args(Rest, Opts#opts{verbose=true}); -parse_args(["--verbose"|Rest], Opts) -> - put(verbose, true), - parse_args(Rest, Opts#opts{verbose=true}); - -parse_args(["--subdiv", N0|Rest], Opts=#opts{modify=Mod}) -> - N = try - list_to_integer(N0) - catch _:_ -> - io:format("**** Error: Option --subdiv ~p Not an integer ~n~n", [N0]), - usage(Opts) - end, - parse_args(Rest, Opts#opts{modify=[{subdivisions, N}|Mod]}); -parse_args(["--tess"++_, "tri"++_|Rest], Opts=#opts{modify=Mod}) -> - parse_args(Rest, Opts#opts{modify=[{tesselation,triangulate}|Mod]}); -parse_args(["--tess"++_, "quad"++_|Rest], Opts=#opts{modify=Mod}) -> - parse_args(Rest, Opts#opts{modify=[{tesselation,quadrangulate}|Mod]}); - -parse_args(["--informat", Format|Rest], Opts) -> - parse_args(Rest, Opts#opts{in_format=check_format(in, Format, Opts)}); -parse_args(["-f", Format|Rest], Opts) -> - parse_args(Rest, Opts#opts{out_module=check_format(out, Format, Opts)}); -parse_args([Opt=[$-|_]| _], Opts) -> - io:format("**** Error: Unknown option ~p~n~n", [Opt]), - usage(Opts); -parse_args(Files, Opts) -> - {Opts, Files}. - -check_format(Dir, Ext = [A|_], Opts) when A =/= $. -> - check_format(Dir, [$.|Ext], Opts); -check_format(in, Ext, O=#opts{in_formats=In}) -> - case get_module(Ext, In) of - error -> - check_format_err(in, Ext, O); - Mod -> Mod - end; -check_format(out, Ext, O=#opts{out_formats=Out}) -> - case get_module(Ext, Out) of - error -> - check_format_err(out, Ext, O); - Mod -> Mod - end. - -check_format_err(Dir, Format, Opts) -> - io:format("**** Error: Format ~p for ~pput is not supported ~n~n", [Format,Dir]), - usage(Opts). - -usage(#opts{in_formats=In, out_formats=Out}) -> - io:format("Usage: wings_convert -f OutFormat [Opts] Files ~n" - " Converts between file formats. ~n" - " Output is written to the current directory by default.~n~n" - " Options:~n" - " -o, --outdir DIR Write converted files to DIR.~n" - " -v, --verbose Verbose output.~n" - " --informat FORMAT Ignore file extension and use FORMAT as input.~n" - " --subdiv N Subdivide object N times (default 0).~n" - " --tess TYPE Tesselate object none|tri|quad (default none)~n" - -%% " --image Convert images" - "~n" - ), - io:format("~nSupported import formats:~n",[]), - [io:format(" ~s~n", [Str]) || #format{str=Str} <- In], - io:format("~nSupported export formats:~n",[]), - [io:format(" ~s~n", [Str]) || #format{str=Str} <- Out], - io:nl(), - halt(1). - -get_module(Ext, [F=#format{str=Str}|List]) -> - case string:str(Str,Ext) of - 0 -> - get_module(Ext,List); - _ -> - F - end; -get_module(_, []) -> - error. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -call_wings(Cmd) -> - verbose("Script: ~p~n",[{action, Cmd}]), - wings ! {action, Cmd}, - Me = self(), - Sync = fun(_St) -> - Me ! {cmd, sync}, - keep - end, - wings ! {external, Sync}, - receive {cmd, sync} -> ok end, - ok. - -import_file(File, Opts) -> - verbose("~s => ~n", [File]), - wings ! {action, fun() -> put(wings_not_running, {import, File}), keep end}, - try import_file_1(File,Opts) of - {error,Reason} -> - io:format("**** Import Failed: ~p On file: ~p~n~n", [Reason, File]), - halt(1); - ok -> - ok - catch - _:{command_error,Message} -> - io:format("**** Import Failed: ~s On file: ~p~n~n", [Message, File]), - halt(1); - _:Reason -> - io:format("**** Import crashed: ~p On file: ~p~n~n", [Reason, File]), - io:format("Debug info: ~p~n~n",[erlang:get_stacktrace()]), - halt(1) - end. - -import_file_1(File, Opts=#opts{in_format=undefined}) -> - import_file_2(filename:extension(File),File,Opts); - -import_file_1(File, Opts=#opts{in_format=InFormat}) -> - import_file_2(InFormat,File,Opts). - -import_file_2(#format{mod=wings}, File, _) -> - call_wings({file, {confirmed_open, File}}); -import_file_2(#format{mod=nendo}, File, _) -> - call_wings({file, {import, {ndo, File}}}); -import_file_2(#format{ext_type=Type, option=false}, _File, _Opts) -> - call_wings({file,{import,Type}}); -import_file_2(#format{ext_type=Type, option=true}, _File, _) -> - call_wings({file,{import,{Type,[]}}}); - -import_file_2(Str, File, Opts = #opts{in_formats=In}) -> - case get_module(Str, In) of - error -> - io:format("**** Error: Import Failed: ~p On file: ~p~n~n", - ["Unknown import format", File]), - halt(1); - Mod -> import_file_2(Mod, File, Opts) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -do_mods(#opts{modify=Mods}) -> - modify_model(Mods). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -export_file(File, Opts=#opts{dir=Dir, out_module=F=#format{ext_type=Ext}}) -> - FileName = filename:join(Dir, File++"."++ atom_to_list(Ext)), - verbose("Export to: ~s~n", [FileName]), - wings ! {action, fun() -> put(wings_not_running, {export, FileName}), keep end}, - - try export_file_1(F, FileName, Opts) of - {error,Reason} -> - io:format("**** Export Failed: ~p On file: ~p~n~n", [Reason, FileName]), - halt(1); - ok -> - ok - catch - _:{command_error,Message} -> - io:format("**** Export Failed: ~s On file: ~p~n~n", [Message, File]), - halt(1); - _:Reason -> - io:format("**** Export crashed: ~p On file: ~p~n~n", [Reason, FileName]), - io:format("Debug info: ~p~n~n",[erlang:get_stacktrace()]), - halt(1) - end. - -export_file_1(F=#format{option=true}, FileName, Opts) -> - export_file_2(F, FileName, Opts); -export_file_1(F, FileName, Opts) -> - export_file_2(F, FileName, Opts). - -export_file_2(#format{mod=wings}, FileName, _Opts) -> - call_wings({file, {save_as, {FileName, ignore}}}); -export_file_2(#format{mod=nendo}, FileName, _Opts) -> - call_wings({file, {export, {ndo, FileName}}}); -export_file_2(#format{ext_type=Type, option=false}, _FN, _Opts) -> - call_wings({file,{export,Type}}); -export_file_2(#format{ext_type=Type, option=true}, _F, _Opts) -> - Mods = [{include_uvs, true}, {include_normals, true}], - call_wings({file,{export,{Type, Mods}}}). - -verbose(F,A) -> - get(verbose) andalso io:format(F,A). - -modify_model([]) -> - ok; -modify_model(Ps) -> - SubDivs = proplists:get_value(subdivisions, Ps, 0), - Tess = proplists:get_value(tesselation, Ps, none), - call_wings({select, body}), - call_wings({select, all}), - sub_divide(SubDivs), - tesselate(Tess), - ok. - -sub_divide(0) -> ok; -sub_divide(N) -> - call_wings({body, smooth}), - sub_divide(N-1). - -tesselate(none) -> ok; -tesselate(triangulate) -> - call_wings({select, face}), - call_wings({face, {tesselate, triangulate}}); -tesselate(quadrangulate) -> - call_wings({select, face}), - call_wings({face, {tesselate, quadrangulate}}). diff -Nru wings3d-2.2.4/src/wings_convert.escript wings3d-2.2.5/src/wings_convert.escript --- wings3d-2.2.4/src/wings_convert.escript 1970-01-01 00:00:00.000000000 +0000 +++ wings3d-2.2.5/src/wings_convert.escript 2019-12-07 15:15:57.000000000 +0000 @@ -0,0 +1,418 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +%% Wings 3D File convertion. +%% +%% Copyright (c) 2010 Dan Gudmundsson +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% + +-mode(compile). +-compile([{nowarn_deprecated_function, {erlang,get_stacktrace,0}}]). + +%% If moved outside of wings directory modify +-define(WINGS_DIR, "c:/src/wings/ebin"). + +-record(opts, + {dir = ".", %% Ouput to directory + out_module, %% Output format + verbose=false, %% Verbose output + in_format, %% In format (if unknown extension). + image_format, %% Image out format + in_formats=[], %% Scanned, all import formats + out_formats=[], %% Scanned, all export formats + modify=[] %% Convertion modifications + }). + +-record(format, + {mod, %% Module + ext_type, %% Extension + str="", %% Description string + option=false %% Allows options + }). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +main(Args) -> + Wings_dir = setup_paths(), + ok = wings_start:start(script_usage), + IEDir = filename:join(Wings_dir, "plugins/import_export"), + code:add_patha(IEDir), + Opts0 = scan_format([IEDir]), + put(verbose, false), + case parse_args(Args, Opts0) of + {#opts{out_module=undefined},_} -> + io:format("**** Error: Out format not specified~n~n"), + usage(Opts0); + {Opts = #opts{}, Files} -> + convert(Files, Opts), + quit(0); + error -> + usage(Opts0) + end. + +convert(Fs, Opts) -> + Convert = fun(File) -> + call_wings({file, confirmed_new}), + ok = import_file(File, Opts), + ok = do_mods(Opts), + Out = filename:rootname(filename:basename(File)), + export_file(Out, Opts) + end, + [Convert(File) || File <- Fs], + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +scan_format(Dir) -> + Files = filelib:wildcard(filename:join(Dir, "wpc_*.beam")), + Plugin = fun(File, {Type, Acc}) -> + Mod = list_to_atom(filename:rootname(filename:basename(File))), + case Mod:menu({file, Type}, []) of + [{Str, ExtT, Extra}] -> + F = #format{mod=Mod, ext_type=ExtT, str = strip(Str), + option = lists:member(option, Extra) + }, + {Type,[F|Acc]}; + [{Str, ExtT}] -> + F = #format{mod=Mod, ext_type=ExtT, str = strip(Str)}, + {Type,[F|Acc]}; + [] -> + {Type, Acc} + end + end, + Default = [#format{mod=nendo, ext_type=ndo, str="Nendo (.ndo)"}, + #format{mod=wings, ext_type=wings, str="Wings (.wings)"}], + {_,Export} = lists:foldl(Plugin, {export, Default}, Files), + {_,Import} = lists:foldl(Plugin, {import, Default}, Files), + + #opts{in_formats=Import, out_formats=Export}. + +strip(Str) -> + strip_1(lists:reverse(Str)). + +strip_1([$.|Rest]) -> + strip_1(Rest); +strip_1(Str) -> + lists:reverse(Str). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +parse_args(["-o", Dir|Rest], Opts) -> + parse_args(Rest, Opts#opts{dir=Dir}); +parse_args(["--outdir", Dir|Rest], Opts) -> + parse_args(Rest, Opts#opts{dir=Dir}); +parse_args(["-v"|Rest], Opts) -> + put(verbose, true), + parse_args(Rest, Opts#opts{verbose=true}); +parse_args(["--verbose"|Rest], Opts) -> + put(verbose, true), + parse_args(Rest, Opts#opts{verbose=true}); + +parse_args(["--subdiv", N0|Rest], Opts=#opts{modify=Mod}) -> + N = try + list_to_integer(N0) + catch _:_ -> + io:format("**** Error: Option --subdiv ~p Not an integer ~n~n", [N0]), + usage(Opts) + end, + parse_args(Rest, Opts#opts{modify=[{subdivisions, N}|Mod]}); +parse_args(["--tess"++_, "tri"++_|Rest], Opts=#opts{modify=Mod}) -> + parse_args(Rest, Opts#opts{modify=[{tesselation,triangulate}|Mod]}); +parse_args(["--tess"++_, "quad"++_|Rest], Opts=#opts{modify=Mod}) -> + parse_args(Rest, Opts#opts{modify=[{tesselation,quadrangulate}|Mod]}); +parse_args(["--name", Name|Rest], Opts=#opts{modify=Mod}) -> + parse_args(Rest, Opts#opts{modify=[{exp_name,Name}|Mod]}); + +parse_args(["--swap_y_z"++_|Rest], Opts=#opts{modify=Mod}) -> + parse_args(Rest, Opts#opts{modify=[{swap_y_z,true}|Mod]}); +parse_args(["--scale", Scale|Rest], Opts=#opts{modify=Mod}) -> + F = try list_to_float(Scale) + catch _:_ -> + try float(list_to_integer(Scale)) + catch _:_ -> + io:format("**** Error: Option --scale ~p not a ~n~n", [Scale]), + usage(Opts) + end + end, + parse_args(Rest, Opts#opts{modify=[{scale, F}|Mod]}); +parse_args(["--uv", Bool|Rest], Opts=#opts{modify=Mod}) -> + parse_args(Rest, Opts#opts{modify=[{include_uvs,bool(Bool, Opts)}|Mod]}); +parse_args(["--n", Bool|Rest], Opts=#opts{modify=Mod}) -> + parse_args(Rest, Opts#opts{modify=[{include_normals,bool(Bool, Opts)}|Mod]}); +parse_args(["--image", Ext|Rest], Opts=#opts{modify=Mod}) -> + parse_args(Rest, Opts#opts{modify=[{image,Ext}|Mod]}); + +parse_args(["--informat", Format|Rest], Opts) -> + parse_args(Rest, Opts#opts{in_format=check_format(in, Format, Opts)}); +parse_args(["-f", Format|Rest], Opts) -> + parse_args(Rest, Opts#opts{out_module=check_format(out, Format, Opts)}); +parse_args([Opt=[$-|_]| _], Opts) -> + io:format("**** Error: Unknown option ~p~n~n", [Opt]), + usage(Opts); +parse_args(Files, Opts) -> + {Opts, Files}. + +check_format(Dir, Ext = [A|_], Opts) when A =/= $. -> + check_format(Dir, [$.|Ext], Opts); +check_format(in, Ext, O=#opts{in_formats=In}) -> + case get_module(Ext, In) of + error -> + check_format_err(in, Ext, O); + Mod -> Mod + end; +check_format(out, Ext, O=#opts{out_formats=Out}) -> + case get_module(Ext, Out) of + error -> + check_format_err(out, Ext, O); + Mod -> Mod + end. + +check_format_err(Dir, Format, Opts) -> + io:format("**** Error: Format ~p for ~pput is not supported ~n~n", [Format,Dir]), + usage(Opts). + +bool(Str, Opts) -> + case string:lowercase(Str) of + "false" -> false; + "true" -> true; + _ -> io:format("**** Error ~p is not a boolean ~n~n",[Str]), + usage(Opts) + end. + +usage(#opts{in_formats=In, out_formats=Out}) -> + io:format("Usage: wings_convert -f OutFormat [Opts] Files ~n" + " Converts between file formats. ~n" + " Output is written to the current directory by default.~n~n" + " Example: wings_convert -f obj --subdiv 1 --tess quad ../model.wings~n~n" + " Options:~n" + " -o, --outdir DIR Write converted files to DIR.~n" + " -v, --verbose Verbose output.~n" + " --informat FORMAT Ignore file extension and use FORMAT as input.~n" + " --subdiv N Subdivide object N times (default 0).~n" + " --tess TYPE Tesselate object none|tri|quad (default none)~n" + " --name Name Only Export Object with Name~n" + " --swap_y_z Swap axis (if supported by exporter) (default false)~n" + " --scale Float Scale (if supported by exporter) (default 1.0)~n" + " --uv Bool Export uv (if supported by exporter) (default true)~n" + " --n Bool Export normals (if supported by exporter) (default true)~n" + " --image Ext Convert images (if supported by exporter) (default .png)~n" + "~n" + ), + io:format("~nSupported import formats:~n",[]), + [io:format(" ~s~n", [Str]) || #format{str=Str} <- In], + io:format("~nSupported export formats:~n",[]), + [io:format(" ~s~n", [Str]) || #format{str=Str} <- Out], + io:nl(), + quit(1). + +quit(Exit) -> + verbose("Script: exiting\n", []), + Ref = monitor(process, wings), + wings ! {action, {file, confirmed_quit}}, + receive + {'DOWN',Ref,_,_,_Reason} -> + timer:sleep(100), + halt(Exit) + after 1000 -> + halt(Exit) + end. + +get_module(Ext, [F=#format{str=Str}|List]) -> + case string:str(Str,Ext) of + 0 -> + get_module(Ext,List); + _ -> + F + end; +get_module(_, []) -> + error. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +call_wings(Cmd) -> + verbose("Script: ~p~n",[{action, Cmd}]), + wings ! {action, Cmd}, + Me = self(), + Sync = fun(_St) -> + Me ! {cmd, sync}, + keep + end, + wings ! {external, Sync}, + receive {cmd, sync} -> ok end, + ok. + +import_file(File, Opts) -> + verbose("~s => ~n", [File]), + wings ! {action, fun() -> put(wings_not_running, {import, File}), keep end}, + try import_file_1(File,Opts) of + {error,Reason} -> + io:format("**** Import Failed: ~p On file: ~p~n~n", [Reason, File]), + quit(1); + ok -> + ok + catch + _:{command_error,Message} -> + io:format("**** Import Failed: ~s On file: ~p~n~n", [Message, File]), + quit(1); + _:Reason -> + io:format("**** Import crashed: ~p On file: ~p~n~n", [Reason, File]), + io:format("Debug info: ~p~n~n",[erlang:get_stacktrace()]), + quit(1) + end. + +import_file_1(File, Opts=#opts{in_format=undefined}) -> + import_file_2(filename:extension(File),File,Opts); + +import_file_1(File, Opts=#opts{in_format=InFormat}) -> + import_file_2(InFormat,File,Opts). + +import_file_2(#format{mod=wings}, File, _) -> + call_wings({file, {confirmed_open, File}}); +import_file_2(#format{mod=nendo}, File, _) -> + call_wings({file, {import, {ndo, File}}}); +import_file_2(#format{ext_type=Type, option=false}, _File, _Opts) -> + call_wings({file,{import,Type}}); +import_file_2(#format{ext_type=Type, option=true}, _File, _) -> + call_wings({file,{import,{Type,[]}}}); + +import_file_2(Str, File, Opts = #opts{in_formats=In}) -> + case get_module(Str, In) of + error -> + io:format("**** Error: Import Failed: ~p On file: ~p~n~n", + ["Unknown import format", File]), + quit(1); + Mod -> import_file_2(Mod, File, Opts) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_mods(#opts{modify=Mods}) -> + modify_model(Mods). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +export_file(File, Opts=#opts{dir=Dir, out_module=F=#format{ext_type=Ext}}) -> + FileName = filename:join(Dir, File++"."++ atom_to_list(Ext)), + verbose("Export to: ~s~n", [FileName]), + wings ! {action, fun() -> put(wings_not_running, {export, FileName}), keep end}, + + try export_file_1(F, FileName, Opts) of + {error,Reason} -> + io:format("**** Export Failed: ~p On file: ~p~n~n", [Reason, FileName]), + quit(1); + ok -> + ok + catch + _:{command_error,Message} -> + io:format("**** Export Failed: ~s On file: ~p~n~n", [Message, File]), + quit(1); + _:Reason -> + io:format("**** Export crashed: ~p On file: ~p~n~n", [Reason, FileName]), + io:format("Debug info: ~p~n~n",[erlang:get_stacktrace()]), + quit(1) + end. + +export_file_1(F, FileName, #opts{modify=Mods}) -> + Opts = pick_export_opts(Mods), + case proplists:get_value(exp_name, Mods, false) of + false -> export_file_2(F, FileName, false, Opts); + _Name -> export_file_2(F, FileName, true, Opts) + end. + +export_file_2(#format{mod=wings}, FileName, Selected, _) -> + call_wings({file, {exp_cmd(save_as, Selected), {FileName, ignore}}}); +export_file_2(#format{mod=nendo}, FileName, Selected, _) -> + call_wings({file, {exp_cmd(export, Selected), {ndo, FileName}}}); +export_file_2(#format{ext_type=Type, option=false}, _FN, Selected, _) -> + call_wings({file,{exp_cmd(export, Selected),Type}}); +export_file_2(#format{ext_type=Type, option=true}, _F, Selected, Opts) -> + call_wings({file,{exp_cmd(export, Selected),{Type, Opts}}}). + +exp_cmd(export, false) -> export; +exp_cmd(export, true) -> export_selected; +exp_cmd(save_as, false) -> save_as; +exp_cmd(save_as, true) -> save_selected. + +pick_export_opts(Mods) -> + [{include_uvs, proplists:get_value(include_uvs, Mods, true)}, + {include_normals, proplists:get_value(include_normals, Mods, true)}, + {swap_y_z, proplists:get_value(swap_y_z, Mods, false)}, + {export_scale, proplists:get_value(scale, Mods, 1.0)}, + {default_filetype, string:lowercase(proplists:get_value(image, Mods, ".png"))} + ]. + +verbose(F,A) -> + get(verbose) andalso io:format(F,A). + +modify_model([]) -> + ok; +modify_model(Ps) -> + SubDivs = proplists:get_value(subdivisions, Ps, 0), + Tess = proplists:get_value(tesselation, Ps, none), + call_wings({select, body}), + case proplists:get_value(exp_name, Ps, false) of + false -> call_wings({select, all}); + Name -> call_wings({select, {by, {by_name_with, Name}}}) + end, + sub_divide(SubDivs), + tesselate(Tess), + ok. + +sub_divide(0) -> ok; +sub_divide(N) -> + call_wings({body, smooth}), + sub_divide(N-1). + +tesselate(none) -> ok; +tesselate(triangulate) -> + call_wings({tools,{virtual_mirror, freeze}}), + call_wings({select, face}), + call_wings({face, {tesselate, triangulate}}); +tesselate(quadrangulate) -> + call_wings({tools,{virtual_mirror, freeze}}), + call_wings({select, face}), + call_wings({face, {tesselate, quadrangulate}}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%% + +setup_paths() -> + Escript = filename:dirname(filename:absname(escript:script_name())), + Installed = installed(Escript), + EnvDir = os:getenv("WINGS_DIR"), + DefDir = ?WINGS_DIR, + case test_paths([Installed, Escript,EnvDir,DefDir]) of + {ok, Path} -> + code:add_patha(filename:join([Path, "ebin"])), + Path; + _ -> + io:format("**** Error: Compiled wings files not found~n~n"), + io:format(" use 'set WINGS_DIR=c:\PATH_TO_WINGS_INSTALL~n~n") + end. + +test_paths([false|Rest]) -> test_paths(Rest); +test_paths([Path0|Rest]) -> + Path = strip_path(lists:reverse(Path0)), + case filelib:is_regular(filename:join([Path, "ebin", "wings.beam"])) of + true -> {ok, Path}; + false -> test_paths(Rest) + end; +test_paths([]) -> not_found. + +strip_path("nibe/" ++ Path) -> lists:reverse(Path); +strip_path("crs/" ++ Path) -> lists:reverse(Path); +strip_path(Path) -> lists:reverse(Path). + +installed(Path) -> + Lib = filename:join(Path, "lib"), + case filelib:wildcard("wings-*", Lib) of + [WingsDir] -> + filename:join(Lib, WingsDir); + [] -> + false; + [_|_] = Strange -> + io:format("Ignore bad installation (please report):~n ~p~n", + [Strange]), + false + end. diff -Nru wings3d-2.2.4/src/wings_dialog.erl wings3d-2.2.5/src/wings_dialog.erl --- wings3d-2.2.4/src/wings_dialog.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_dialog.erl 2019-12-07 15:15:57.000000000 +0000 @@ -33,7 +33,7 @@ %%-compile(export_all). -record(in, {key, type, def, wx, wx_ext=[], validator, data, hook, output=true}). --record(eh, {fs, apply, prev_parent, owner, type, pid, dialog}). +-record(eh, {fs, apply, prev_parent, owner, type, pid, dialog, timer}). %% %% Syntax of Qs. @@ -209,7 +209,7 @@ info(Title, Info, Options) -> Parent = proplists:get_value(parent, Options, get_dialog_parent()), Flags = [{size, {500, 400}}, {style, ?wxCAPTION bor ?wxRESIZE_BORDER bor ?wxCLOSE_BOX}], - Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, Flags), + Frame = wxFrame:new(Parent, ?wxID_ANY, Title, Flags), Panel = wxHtmlWindow:new(Frame, []), Sizer = wxBoxSizer:new(?wxVERTICAL), Html = text_to_html(Info), @@ -513,7 +513,8 @@ receive {'DOWN',Ref,process,_,_} -> ok end. event_handler(#wx{id=?wxID_CANCEL}, - #eh{apply=Fun, owner=Owner, type=Preview, pid=Pid}) -> + #eh{apply=Fun, owner=Owner, type=Preview, pid=Pid}=Eh0) -> + reset_timer(Eh0), wings_wm:release_focus(), case Preview of preview -> @@ -525,8 +526,9 @@ close(Pid), delete; event_handler(#wx{id=Result}=_Ev, - #eh{fs=Fields, apply=Fun, owner=Owner, pid=Pid}) -> + #eh{fs=Fields, apply=Fun, owner=Owner, pid=Pid} = Eh0) -> %%io:format("Ev closing ~p~n ~p~n",[_Ev, Fields]), + reset_timer(Eh0), wings_wm:release_focus(), Values = get_output(Result, Fields), close(Pid), @@ -537,7 +539,12 @@ io:format("Dialog preview crashed: ~p~n~p~n",[Reason, erlang:get_stacktrace()]) end, delete; -event_handler(preview, #eh{fs=Fields, apply=Fun, owner=Owner}) -> +event_handler(preview, Eh0) -> + Eh = reset_timer(Eh0), + New = wings_wm:set_timer(150, preview_exec), + {replace, fun(Ev) -> event_handler(Ev, Eh#eh{timer=New}) end}; +event_handler(preview_exec, #eh{fs=Fields, apply=Fun, owner=Owner}=Eh0) -> + Eh = reset_timer(Eh0), Values = get_output(preview, Fields), try Fun({dialog_preview,Values}) of {preview,#st{}=St0,#st{}=St} -> @@ -545,17 +552,16 @@ wings_wm:send(Owner, {current_state,St0}); {preview,Action,#st{}=St}-> wings_wm:send_once_after_redraw(Owner, {action,Action}), - wings_wm:send(Owner, {current_state,St}), - keep; + wings_wm:send(Owner, {current_state,St}); Action = {numeric_preview, _} -> wings_wm:send(Owner, {action,Action}); Action when is_tuple(Action); is_atom(Action) -> %%io:format("~p:~p: ~p~n",[?MODULE,?LINE,{preview,[Owner,{action,Action}]}]), wings_wm:send(Owner, {action,Action}) catch _:Reason -> - io:format("Dialog preview crashed: ~p~n~p~n",[Reason, erlang:get_stacktrace()]), - keep - end; + io:format("Dialog preview crashed: ~p~n~p~n",[Reason, erlang:get_stacktrace()]) + end, + {replace, fun(Ev) -> event_handler(Ev, Eh) end}; event_handler(#mousebutton{which=Obj}=Ev, _) -> wings_wm:send(wings_wm:wx2win(Obj), {camera,Ev,keep}); event_handler(#mousemotion{}, _) -> keep; @@ -567,6 +573,12 @@ %% io:format("unhandled Ev ~p~n",[_Ev]), keep. +reset_timer(#eh{timer=undefined} = Eh) -> + Eh; +reset_timer(#eh{timer=Timer}=Eh) -> + wings_wm:cancel_timer(Timer), + Eh#eh{timer=undefined}. + get_output(Result, {Table, Order}) -> Get = fun(Key, Acc) -> case ets:lookup(Table, Key) of @@ -1610,31 +1622,34 @@ wxSizer:add(Sizer, Ctrl, [{proportion, Proportion},{border, Border},{flag, Flags}]). sizer_flags(label, ?wxHORIZONTAL) -> {0, 2, ?wxRIGHT bor ?wxALIGN_CENTER_VERTICAL}; -sizer_flags(label, ?wxVERTICAL) -> {1, 2, ?wxRIGHT bor ?wxALIGN_CENTER_VERTICAL}; +sizer_flags(label, ?wxVERTICAL) -> {1, 2, ?wxRIGHT }; sizer_flags(separator, ?wxHORIZONTAL) -> {1, 5, ?wxALL bor ?wxALIGN_CENTER_VERTICAL}; sizer_flags(separator, ?wxVERTICAL) -> {0, 5, ?wxALL bor ?wxEXPAND}; sizer_flags(text, ?wxHORIZONTAL) -> {1, 2, ?wxRIGHT bor ?wxALIGN_CENTER_VERTICAL}; sizer_flags(slider, ?wxHORIZONTAL) -> {2, 0, ?wxALIGN_CENTER_VERTICAL}; sizer_flags(slider, ?wxVERTICAL) -> {0, 0, ?wxEXPAND}; -sizer_flags(button, _) -> {0, 0, ?wxALIGN_CENTER_VERTICAL}; -sizer_flags(image, _) -> {0, 5, ?wxALL bor ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}; -sizer_flags(choice, _) -> {0, 0, ?wxALIGN_CENTER_VERTICAL}; -sizer_flags(checkbox, ?wxVERTICAL) -> {0, 3, ?wxTOP bor ?wxBOTTOM bor ?wxALIGN_CENTER_VERTICAL}; +sizer_flags(button, Dir) -> {0, 0, center_v(Dir)}; +sizer_flags(image, _Dir) -> {0, 5, ?wxALL bor ?wxEXPAND}; +sizer_flags(choice, Dir) -> {0, 0, center_v(Dir)}; +sizer_flags(checkbox, ?wxVERTICAL) -> {0, 3, ?wxTOP bor ?wxBOTTOM }; sizer_flags(checkbox, ?wxHORIZONTAL) -> {0, 2, ?wxRIGHT bor ?wxALIGN_CENTER_VERTICAL}; sizer_flags(table, _) -> {4, 0, ?wxEXPAND}; -sizer_flags({radiobox, Dir}, Dir) -> {5, 0, ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}; -sizer_flags({radiobox, _}, _) -> {1, 0, ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}; -sizer_flags({box, Dir}, Dir) -> {0, 2, ?wxALL bor ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}; -sizer_flags({box, _}, _) -> {0, 2, ?wxALL bor ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}; +sizer_flags({radiobox, Dir}, Dir) -> {5, 0, ?wxEXPAND}; +sizer_flags({radiobox, _}, _Dir) -> {1, 0, ?wxEXPAND}; +sizer_flags({box, Dir}, Dir) -> {0, 2, ?wxALL bor ?wxEXPAND}; +sizer_flags({box, _}, _Dir) -> {0, 2, ?wxALL bor ?wxEXPAND}; sizer_flags(fontpicker, ?wxHORIZONTAL) -> {2, 2, ?wxRIGHT}; sizer_flags(fontpicker, ?wxVERTICAL) -> {0, 2, ?wxRIGHT bor ?wxEXPAND}; sizer_flags(filepicker, ?wxHORIZONTAL) -> {2, 2, ?wxRIGHT}; sizer_flags(filepicker, ?wxVERTICAL) -> {0, 2, ?wxRIGHT bor ?wxEXPAND}; sizer_flags(custom, _) -> {0, 5, ?wxALL}; -sizer_flags(panel, _) -> {0, 0, ?wxALL bor ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}; %?wxEXPAND}; +sizer_flags(panel, _Dir) -> {0, 0, ?wxALL bor ?wxEXPAND}; sizer_flags(_, ?wxHORIZONTAL) -> {1, 0, ?wxALIGN_CENTER_VERTICAL}; sizer_flags(_, ?wxVERTICAL) -> {0, 0, ?wxEXPAND}. +center_v(?wxHORIZONTAL) -> ?wxALIGN_CENTER_VERTICAL; +center_v(?wxVERTICAL) -> 0. + create(false, _) -> undefined; create(_, Fun) -> Fun(). @@ -1964,6 +1979,9 @@ fix_expr([$.],Acc) -> lists:reverse(Acc, "."); fix_expr([$.|T], [X|_]=Acc) when X >= $0, X =< $9 -> fix_expr(T, [$.|Acc]); +%% Translate float 2,4 to erlangish 2.4 +fix_expr([$, |[Y|_]=T], [X|_]=Acc) when X >= $0, X =< $9, Y >= $0, Y =< $9 -> + fix_expr(T, [$.|Acc]); fix_expr([$.|T], Acc) -> fix_expr(T, [$.,$0|Acc]); %% Some math simplifications. diff -Nru wings3d-2.2.4/src/wings_drag.erl wings3d-2.2.5/src/wings_drag.erl --- wings3d-2.2.4/src/wings_drag.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_drag.erl 2019-12-07 15:15:57.000000000 +0000 @@ -28,7 +28,9 @@ %% Main drag record. Kept in state. -record(drag, - {x,y, % Original 2D position, + {xy0, % Original 2D position, + xy, % Orig or since last mouse (search no_warp) + warp, % true or Win Size xs=0,ys=0, % Summary of mouse movements zs=0, % Z move in screen relative p4=0,p5=0, % An optional forth drag parameter @@ -200,10 +202,15 @@ UnitSc = unit_scales(Units), Falloff = falloff(Units), {ModeFun,ModeData} = setup_mode(Flags, Falloff), - #drag{unit=Units,unit_sc=UnitSc,flags=Flags,offset=Offset, - falloff=Falloff, - mode_fun=ModeFun,mode_data=ModeData, - st=St,drag=DragFun}. + Warp = case wings_pref:get_value(no_warp, false) of + false -> true; + true -> wings_wm:win_size() + end, + #drag{warp=Warp, + unit=Units,unit_sc=UnitSc,flags=Flags,offset=Offset, + falloff=Falloff, + mode_fun=ModeFun,mode_data=ModeData, + st=St,drag=DragFun}. fold_1([{Id,Items}|T], F, Shapes0) -> We0 = gb_trees:get(Id, Shapes0), @@ -481,11 +488,11 @@ {_,X,Y} = wings_wm:local_mouse_state(), Ev = #mousemotion{x=X,y=Y,state=0}, wings_tweak:toggle_draw(false), - {seq,push,handle_drag_event_2(Ev, Drag#drag{x=X,y=Y})}; + {seq,push,handle_drag_event_2(Ev, start_pos(X, Y, Drag))}; do_drag(#drag{unit=Units}=Drag0, Move) when length(Units) =:= length(Move) -> {_,X,Y} = wings_wm:local_mouse_state(), Ev = #mousemotion{x=X,y=Y,state=0}, - Drag1 = motion(Ev, Drag0#drag{x=X,y=Y}), + Drag1 = motion(Ev, start_pos(X,Y,Drag0)), ungrab(Drag1), Drag2 = possible_falloff_update(Move, Drag1), Drag = ?SLOW(motion_update(Move, Drag2)), @@ -496,7 +503,7 @@ do_drag(Drag0, _) -> wings_menu:kill_menus(), {_,X,Y} = wings_wm:local_mouse_state(), - ungrab(Drag0#drag{x=X,y=Y}), + ungrab(start_pos(X,Y,Drag0)), wings_wm:later(revert_state), keep. @@ -758,7 +765,7 @@ get_drag_event(Drag); handle_drag_event_2({numeric_preview,Move}, Drag0) -> {_,X,Y} = wings_wm:local_mouse_state(), - Drag1 = possible_falloff_update(Move, Drag0#drag{x=X,y=Y}), + Drag1 = possible_falloff_update(Move, start_pos(X,Y,Drag0)), Drag = ?SLOW(motion_update(Move, Drag1)), get_drag_event(Drag); handle_drag_event_2({drag_arguments,Move}, Drag0) -> @@ -815,7 +822,7 @@ wings_wm:later(DragEnded), pop. -ungrab(#drag{x=Ox,y=Oy}) -> +ungrab(#drag{xy0={Ox,Oy}}) -> wings_wm:release_focus(), wings_io:ungrab(Ox, Oy). @@ -920,9 +927,10 @@ wings_dl:map(fun view_changed_fun/2, []), case member(keep_drag,Flags) of true -> - Drag0#drag{x=X,y=Y}; + start_pos(X,Y,Drag0); false -> - Drag0#drag{x=X,y=Y,xs=0,ys=0,zs=0,p4=0,p5=0,psum=[0,0,0,0,0]} + Drag1 = start_pos(X,Y,Drag0), + Drag1#drag{xs=0,ys=0,zs=0,p4=0,p5=0,psum=[0,0,0,0,0]} end end. @@ -936,6 +944,11 @@ {D#dlo{drag=Do#do{tr_fun=F}},[]}; view_changed_fun(D, _) -> {D,[]}. + +start_pos(X,Y,Drag) -> + XY0 = {X,Y}, + Drag#drag{xy=XY0, xy0=XY0}. + motion(Event, Drag0) -> {Move,Drag} = mouse_translate(Event, Drag0), motion_update(Move, Drag). @@ -943,99 +956,108 @@ mouse_translate(Event0, Drag0) -> Mode = wings_pref:get_value(camera_mode), {Event,Mod,Drag1} = mouse_pre_translate(Mode, Event0,Drag0), - mouse_range(Event, Drag1, Mod). + mouse_range(Event, Mod, Drag1). mouse_pre_translate(Mode, #mousemotion{state=Mask,mod=Mod}=Ev,Drag) when Mode=:=blender; Mode=:=sketchup; Mode=:=tds -> if - Mask band ?SDL_BUTTON_RMASK =/= 0, - Mod band ?CTRL_BITS =/= 0 -> - {Ev#mousemotion{state=?SDL_BUTTON_MMASK}, - Mod band (bnot ?CTRL_BITS),Drag}; - true -> {Ev,Mod,Drag} + Mask band ?SDL_BUTTON_RMASK =/= 0, + Mod band ?CTRL_BITS =/= 0 -> + {Ev#mousemotion{state=?SDL_BUTTON_MMASK}, + Mod band (bnot ?CTRL_BITS),Drag}; + true -> {Ev,Mod,Drag} end; mouse_pre_translate(_, #mousemotion{mod=Mod}=Ev,Drag) -> {Ev,Mod,Drag}. -mouse_range(#mousemotion{x=X, y=Y, state=Mask}, - #drag{x=OX, y=OY, - xs=Xs0, ys=Ys0, zs=Zs0, p4=P4th0,p5=P5th0, - psum=Psum0, - mode_data=MD, - xt=Xt0, yt=Yt0, mmb_timer=Count0, - unit_sc=UnitScales, unit=Unit, offset=Offset, - last_move=LastMove}=Drag0, Mod) -> - %%io:format("Mouse Range ~p ~p~n", [{X0,Y0}, {OX,OY,Xs0,Ys0}]), - [Xp,Yp,Zp,P4thp,P5thp] = - case Mod =/= 0 of - true -> Psum0; - false -> [Xs0,Ys0,Zs0,P4th0,P5th0] - end, - +mouse_range(#mousemotion{x=X, y=Y, state=Mask}, Mod, + #drag{xy={OX, OY},mode_data=MD0,last_move=LastMove}=Drag0) -> case wings_pref:lowpass(X-OX, Y-OY) of - {0,0} -> - Drag = Drag0#drag{xt=0,yt=0}, - {{no_change,LastMove},Drag}; - - {XD0,YD0} -> - CS = constraints_scale(Unit,Mod,UnitScales), - XD = CS*(XD0 + Xt0), - YD = CS*(YD0 + Yt0), - - ModeData = - case MD of - {_,MD0} -> MD0; - MD0 -> MD0 - end, - ParaNum = length(Unit), - case Mask of - ?SDL_BUTTON_LEFT when ParaNum >= 3 -> - Xs = {no_con, Xs0}, - Ys = {no_con, -Ys0}, - Zs = {con, - (Zp - XD)}, %Horizontal motion - P4th = {no_con, -P4th0}, - P5th = {no_con, -P5th0}, - Count = Count0; - ?SDL_BUTTON_MMASK when ParaNum >= 4 -> - Xs = {no_con, Xs0}, - Ys = {no_con, -Ys0}, - Zs = {no_con, -Zs0}, - P4th = {con, - (P4thp + XD)}, - P5th = {no_con, -P5th0}, - Count = Count0 + 1; - ?SDL_BUTTON_RMASK when ParaNum >= 5 -> - Xs = {no_con, Xs0}, - Ys = {no_con, -Ys0}, - Zs = {no_con, -Zs0}, - P4th = {no_con, -P4th0}, - P5th = {con, - (P5thp + XD)}, - Count = Count0; - _ -> - Xs = {con, Xp + XD}, - Ys = {con, - (Yp + YD)}, - Zs = {no_con, -Zs0}, - P4th = {no_con, -P4th0}, - P5th = {no_con, -P5th0}, - Count = Count0 - end, - wings_io:warp(OX, OY), - - % Ds means DragSummary - Ds0 = mouse_scale([Xs,Ys,Zs,P4th,P5th], UnitScales), - Ds1 = add_offset_to_drag_sum(Ds0, Unit, Offset), - Ds = round_to_constraint(Unit, Ds1, Mod, []), - - Psum = [S || {_,S} <- [Xs,Ys,Zs,P4th,P5th]], - [Xs2,Ys2,Zs2,P4th2,P5th2] = constrain_2(Unit, Psum, UnitScales, Offset), - - [Xs1,Ys1,Zs1,P4th1,P5th1] = scale_mouse_back(Ds, UnitScales, Offset), - Move = constrain_1(Unit, Ds, Drag0), - Drag = Drag0#drag{xs=Xs1,ys=-Ys1,zs=-Zs1,p4=-P4th1,p5=-P5th1, - psum=[Xs2,-Ys2,-Zs2,-P4th2,-P5th2], - xt=XD0,yt=YD0,mmb_timer=Count,mode_data=ModeData}, - {Move,Drag} + {0,0} -> + Drag = Drag0#drag{xt=0,yt=0}, + {{no_change,LastMove},Drag}; + {XD0,YD0} -> + MD = case MD0 of + {_, MD1} -> MD1; + MD1 -> MD1 + end, + mouse_range(X, Y, Mask, Mod, XD0, YD0, Drag0#drag{mode_data=MD}) end. +mouse_range(X, Y, Mask, Mod, XD0, YD0, + #drag{xs=Xs0, ys=Ys0, zs=Zs0, p4=P4th0,p5=P5th0, + psum=Psum0, + xt=Xt0, yt=Yt0, mmb_timer=Count0, + unit_sc=UnitScales, unit=Unit, offset=Offset}=Drag0) -> + + CS = constraints_scale(Unit,Mod,UnitScales), + XD = CS*(XD0 + Xt0), + YD = CS*(YD0 + Yt0), + + ParaNum = length(Unit), + [Xp,Yp,Zp,P4thp,P5thp] = + case Mod =/= 0 of + true -> Psum0; + false -> [Xs0,Ys0,Zs0,P4th0,P5th0] + end, + case Mask of + ?SDL_BUTTON_LEFT when ParaNum >= 3 -> + Xs = {no_con, Xs0}, + Ys = {no_con, -Ys0}, + Zs = {con, - (Zp - XD)}, %Horizontal motion + P4th = {no_con, -P4th0}, + P5th = {no_con, -P5th0}, + Count = Count0; + ?SDL_BUTTON_MMASK when ParaNum >= 4 -> + Xs = {no_con, Xs0}, + Ys = {no_con, -Ys0}, + Zs = {no_con, -Zs0}, + P4th = {con, - (P4thp + XD)}, + P5th = {no_con, -P5th0}, + Count = Count0 + 1; + ?SDL_BUTTON_RMASK when ParaNum >= 5 -> + Xs = {no_con, Xs0}, + Ys = {no_con, -Ys0}, + Zs = {no_con, -Zs0}, + P4th = {no_con, -P4th0}, + P5th = {con, - (P5thp + XD)}, + Count = Count0; + _ -> + Xs = {con, Xp + XD}, + Ys = {con, - (Yp + YD)}, + Zs = {no_con, -Zs0}, + P4th = {no_con, -P4th0}, + P5th = {no_con, -P5th0}, + Count = Count0 + end, + %% Ds means DragSummary + Ds0 = mouse_scale([Xs,Ys,Zs,P4th,P5th], UnitScales), + Ds1 = add_offset_to_drag_sum(Ds0, Unit, Offset), + Ds = round_to_constraint(Unit, Ds1, Mod, []), + + Psum = [S || {_,S} <- [Xs,Ys,Zs,P4th,P5th]], + [Xs2,Ys2,Zs2,P4th2,P5th2] = constrain_2(Unit, Psum, UnitScales, Offset), + + [Xs1,Ys1,Zs1,P4th1,P5th1] = scale_mouse_back(Ds, UnitScales, Offset), + Move = constrain_1(Unit, Ds, Drag0), + Drag1 = Drag0#drag{xs=Xs1,ys=-Ys1,zs=-Zs1,p4=-P4th1,p5=-P5th1, + psum=[Xs2,-Ys2,-Zs2,-P4th2,-P5th2], + xt=XD0,yt=YD0,mmb_timer=Count}, + Drag = mouse_warp(X, Y, Drag1), + {Move,Drag}. + +mouse_warp(_X, _Y, #drag{warp=true, xy={OX,OY}} = Drag0) -> + wings_io:warp(OX, OY), + Drag0; +mouse_warp(X,Y, #drag{warp={W,H}} = Drag) + when X < 10 orelse Y < 10 orelse X > (W-10) orelse Y > (H-10) -> + %% Warp at the window edges + Cx = W div 2, Cy = H div 2, + wings_io:warp(Cx, Cy), + Drag#drag{xy={Cx,Cy}}; +mouse_warp(X,Y, Drag) -> + Drag#drag{xy={X,Y}}. + mouse_scale([{Tag,D}|Ds], [S|Ss]) -> [{Tag,D*S}|mouse_scale(Ds, Ss)]; mouse_scale(Ds, _) -> Ds. diff -Nru wings3d-2.2.4/src/wings.erl wings3d-2.2.5/src/wings.erl --- wings3d-2.2.4/src/wings.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings.erl 2019-12-07 15:15:57.000000000 +0000 @@ -12,7 +12,7 @@ %% -module(wings). --export([start/0,start_link/1, init/1]). +-export([start/0, start_link/1, init/1, is_fast_start/0]). -export([redraw/1,redraw/2,init_opengl/1,command/2]). -export([mode_restriction/1,clear_mode_restriction/0,get_mode_restriction/0]). -export([ask/3]). @@ -53,11 +53,12 @@ process_flag(trap_exit, true), register(wings, self()), erlang:system_flag(backtrace_depth, 25), + Args = application:get_env(wings, args), wx:set_env(Env), wings_pref:init(), wings_hotkey:set_default(), - wings_pref:load(), + is_fast_start(Args) orelse wings_pref:load(), wings_lang:init(), wings_plugin:init(), wings_sel_cmd:init(), @@ -66,13 +67,14 @@ %% Ack that we are done, need to create top window now proc_lib:init_ack(self()), Frame = receive {frame_created, Window} -> Window end, + is_fast_start(Args) andalso wxTopLevelWindow:iconize(Frame), GeomGL = wings_gl:init(Frame), wx_object:get_pid(Frame) ! opengl_initialized, %% Wait for other mandatory processes to become initialized receive supervisor_initialization_done -> ok end, - init_part2(Frame, GeomGL). + init_part2(Args, Frame, GeomGL). -init_part2(Frame, GeomGL) -> +init_part2(Args, Frame, GeomGL) -> St0 = new_st(), St1 = wings_sel:reset(St0), St2 = wings_undo:init(St1), @@ -95,14 +97,14 @@ wings_view:init(), wings_u:caption(St), - wings_file:init_autosave(), wings_dialog:init(), wings_job:init(), wings_tweak:init(), - open_file(), + is_fast_start(Args) orelse open_file(Args), make_geom_window(GeomGL, St), - restore_windows(St), + is_fast_start(Args) orelse wings_file:init_autosave(), + is_fast_start(Args) orelse restore_windows(St), case catch wings_wm:enter_event_loop() of {'EXIT',shutdown} -> wings_io:quit(), @@ -211,7 +213,7 @@ true -> free_viewer_num(N+1) end. -open_file() -> +open_file(Args) -> USFile = wings_file:autosave_filename(wings_file:unsaved_filename()), case filelib:is_file(USFile) of true -> @@ -223,13 +225,13 @@ end, fun() -> wings_file:del_unsaved_file(), - open_file_start() + open_file_start(Args) end); - _ -> open_file_start() + _ -> open_file_start(Args) end. -open_file_start() -> - File1 = case application:get_env(wings, args) of +open_file_start(Args) -> + File1 = case Args of undefined -> none; {ok, File0} -> case filelib:is_regular(File0) of @@ -252,6 +254,12 @@ wings_wm:send_after_redraw(geom, {open_file,File}) end. +is_fast_start() -> + is_fast_start(application:get_env(wings, args)). + +is_fast_start({ok, script_usage}) -> true; +is_fast_start(_) -> false. + init_opengl(St) -> wings_render:init(), wings_dl:init(), @@ -682,9 +690,16 @@ command_response(keep, _, _) -> keep; command_response(quit, _, _) -> - save_windows(), - wings_pref:finish(), - exit(shutdown). + case is_fast_start() of + true -> + ignore; + false -> + save_windows(), + wings_pref:finish() + end, + erlang:process_flag(trap_exit, false), + sys:terminate(wings_sup, shutdown), %% async + receive hang_until_killed -> ok end. remember_command({C,_}=Cmd, St) when C =:= vertex; C =:= edge; C =:= face; C =:= body -> diff -Nru wings3d-2.2.4/src/wings_ff_wings.erl wings3d-2.2.5/src/wings_ff_wings.erl --- wings3d-2.2.4/src/wings_ff_wings.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_ff_wings.erl 2019-12-07 15:15:57.000000000 +0000 @@ -12,7 +12,7 @@ %% -module(wings_ff_wings). --export([import/2,merge/2,export/2]). +-export([import/2,merge/2,export/3]). -include("wings.hrl"). -include_lib("wings/e3d/e3d_image.hrl"). @@ -1231,19 +1231,19 @@ %%% Save a Wings file (in version 2). %%% -export(Name, St0) -> +export(Name, OnlySel, St0) -> wings_pb:start( ?__(1,"saving")), wings_pb:update(0.01, ?__(2,"lights")), Lights = wings_light:export_bc(St0), Materials = case wings_pref:get_value(save_unused_materials) of - true -> - #st{mat=Mat} = St0, - gb_trees:to_list(Mat); - false -> - wings_material:used_materials(St0) - end, - #st{shapes=Shs0,views={CurrentView,_}} = St = - remove_lights(St0), + true -> + #st{mat=Mat} = St0, + gb_trees:to_list(Mat); + false -> + wings_material:used_materials(St0) + end, + #st{shapes=Shs0,views={CurrentView,_}} = St = + remove_lights(St0), Sel0 = collect_sel(St), wings_pb:update(0.65, ?__(3,"renumbering")), Shs1 = [{Id,show_mirror_face(We)} || @@ -1257,8 +1257,17 @@ [_|_] -> [{lights,Lights}|Props0] end, Props2 = case export_images() of - [] -> Props1; - Images -> [{images,Images}|Props1] + [] -> Props1; + Images -> + case OnlySel of + true -> + case used_images(Images, Materials) of + [] -> Props1; + Imgs -> [{images,Imgs}|Props1] + end; + false -> + [{images,Images}|Props1] + end end, Props3 = case wings_view:export_views(St) of [] -> Props2; @@ -1316,6 +1325,22 @@ renumber([], [], _NewId, WeAcc, RootAcc) -> {WeAcc,RootAcc}. +used_images(Images, Materials) -> + UsedImgs = + lists:foldl(fun({_,MtlProp}, Acc) -> + case proplists:get_value(maps,MtlProp,[]) of + [] -> Acc; + Map -> + lists:foldl(fun({_Type,Id}, Acc0) -> + gb_sets:add(Id,Acc0) + end, Acc, Map) + end + end, gb_sets:new(), Materials), + case gb_sets:to_list(UsedImgs) of + [] -> []; + Imgs -> [{K,proplists:get_value(K,Images)} || K <- Imgs] + end. + export_props(Sel0) -> Sel1 = sofs:family(Sel0, [{id,[{mode,list,key}]}]), Sel2 = sofs:family_to_relation(Sel1), diff -Nru wings3d-2.2.4/src/wings_file.erl wings3d-2.2.5/src/wings_file.erl --- wings3d-2.2.4/src/wings_file.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_file.erl 2019-12-07 15:15:57.000000000 +0000 @@ -436,7 +436,7 @@ end, file:rename(Name, Backup), file:delete(autosave_filename(Name)), - case ?SLOW(wings_ff_wings:export(Name, St)) of + case ?SLOW(wings_ff_wings:export(Name, false, St)) of ok -> set_cwd(dirname(Name)), add_recent(Name), @@ -474,7 +474,7 @@ save_selected(Name, St0) -> St = delete_unselected(St0), - case ?SLOW(wings_ff_wings:export(Name, St)) of + case ?SLOW(wings_ff_wings:export(Name, true, St)) of ok -> keep; {error,Reason} -> wings_u:error_msg(Reason) end. @@ -546,7 +546,10 @@ {ok,#file_info{mtime=AutoInfo0}} -> SaveTime = calendar:datetime_to_gregorian_seconds(SaveTime0), AutoTime = calendar:datetime_to_gregorian_seconds(AutoInfo0), + FastStart = wings:is_fast_start(), if + FastStart -> %% Ignore autosave + Body(File); AutoTime > SaveTime -> Msg = ?__(1,"An autosaved file with a later time stamp exists;" " do you want to load the autosaved file instead?"), @@ -615,7 +618,7 @@ View = wings_wm:get_prop(geom, current_view), wings_view:set_current(View), filelib:ensure_dir(Auto), - case ?SLOW(wings_ff_wings:export(Auto, St)) of + case ?SLOW(wings_ff_wings:export(Auto, false, St)) of ok -> wings_u:caption(St#st{saved=auto}); {error,Reason} -> diff -Nru wings3d-2.2.4/src/wings_frame.erl wings3d-2.2.5/src/wings_frame.erl --- wings3d-2.2.4/src/wings_frame.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_frame.erl 2019-12-07 15:15:57.000000000 +0000 @@ -11,12 +11,12 @@ -module(wings_frame). --export([top_menus/0, make_win/2, register_win/3, close/1, set_focus/1,set_title/2, +-export([make_win/2, register_win/3, close/1, set_focus/1,set_title/2, get_top_frame/0, show_toolbar/1, export_layout/0, import_layout/2, reinit_layout/0, get_overlay/0, overlay_draw/3, overlay_hide/1, - get_icon_images/0, get_colors/0, update_theme/0]). + get_icon_images/0, get_colors/0, get_border/0, update_theme/0]). -export([start_link/0, forward_event/1]). @@ -49,7 +49,7 @@ ?SET(top_frame, Frame), {ok, wx_object:get_pid(Frame)}. -top_menus() -> +top_menus(WorkAround) -> Tail0 = [{?__(7,"Help"),help,wings_help:menu()}], Tail = case wings_pref:get_value(show_develop_menu) of true -> @@ -57,12 +57,17 @@ false -> Tail0 end, + WinStr = case WorkAround of + true -> ?__(16,"Windows"); %% Temporary + false -> ?__(6,"Window") + end, [{?__(1,"File"), file,wings_file:menu()}, {?__(2,"Edit"), edit,wings:edit_menu()}, {?__(3,"View"), view,wings_view:menu()}, {?__(4,"Select"),select,wings_sel_cmd:menu()}, {?__(5,"Tools"), tools, wings:tools_menu()}, - {?__(6,"Window"),window,wings:window_menu()}|Tail]. + {WinStr,window,wings:window_menu()} + |Tail]. make_win(Title, Opts) -> case proplists:get_value(internal, Opts, false) of @@ -85,10 +90,17 @@ false -> [] end, - Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, [FStyle|Opts]), + Frame = (useframe()):new(Parent, ?wxID_ANY, Title, [FStyle|Opts]), Size =/= false andalso wxWindow:setClientSize(Frame, Size), Frame. +useframe() -> + %% Miniframes can't be resized in gtk and wxWidgets 3.1 + case {os:type(), {?wxMAJOR_VERSION, ?wxMINOR_VERSION}} of + {{_, linux}, Ver} when Ver > {3,0} -> wxFrame; + _ -> wxMiniFrame + end. + register_win(Window, Name, Ps) -> wx_object:call(?MODULE, {new_window, Window, Name, Ps}). @@ -135,6 +147,18 @@ hl_text => wings_color:rgb4bv(wings_pref:get_value(outliner_geograph_hl_text)) }. +get_border() -> + %% Alternatives are: + %% ?wxBORDER_DEFAULT ?wxBORDER_THEME + %% ?wxBORDER_SUNKEN ?wxBORDER_RAISED + %% ?wxBORDER_STATIC ?wxBORDER_SIMPLE + %% ?wxBORDER_NONE + case os:type() of + {win32, _} -> ?wxBORDER_NONE; + {_, darwin} -> ?wxBORDER_NONE; + _ -> ?wxBORDER_NONE + end. + update_theme() -> wx_object:call(?MODULE, update_theme). @@ -296,12 +320,15 @@ TopSize = wings_pref:get_value(window_size), Frame0 = wxFrame:new(wx:null(), -1, "Wings 3D", [{size, TopSize}]), Frame = wx_object:set_pid(Frame0, self()), + IconImgs = make_icons(), set_icon(Frame), Sizer = wxBoxSizer:new(?wxVERTICAL), + Top = make(Frame), Canvas = make_splash(wxPanel:new(win(Top)), IconImgs), - wxSizer:add(Sizer, win(Top), [{proportion, 1}, {flag, ?wxEXPAND}]), + wxSizer:add(Sizer, win(Top), [{proportion, 1}, {border, 0}, + {flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}]), wxSplitterWindow:initialize(win(Top), Canvas), Toolbar = wings_toolbar:init(Frame, IconImgs), wxSizer:setSizeHints(Sizer, win(Top)), @@ -324,6 +351,11 @@ end. make_splash(Canvas, Imgs) -> + BG = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOWFRAME), + case os:type() of %% Workaround black panel color on gtk and wxWidgets-3.1.3 + {_, linux} -> wxWindow:setBackgroundColour(Canvas, BG); + _ -> ok + end, Szr = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:addStretchSpacer(Szr), {Splash, _} = wings_help:about_panel(Canvas,Imgs), @@ -421,7 +453,7 @@ Geom = proplists:get_value(top, Ps), Win0 = #win{win=Window, name=Name}, if External -> - Frame = wx:typeCast(wxWindow:getParent(Window), wxMiniFrame), + Frame = wx:typeCast(wxWindow:getParent(Window), useframe()), Title = wxFrame:getTitle(Frame), Win = Win0#win{frame=Frame, title=Title, ps=#{close=>true, move=>true}}, wxWindow:connect(Frame, move), @@ -571,13 +603,16 @@ update_active(Name, #state{active=Prev, windows=#{ch:=Root}}=State) -> ABG = wings_color:rgb4bv(wings_pref:get_value(title_active_color)), PBG = wings_color:rgb4bv(wings_pref:get_value(title_passive_color)), - TFG = wings_color:rgb4bv(wings_pref:get_value(title_text_color)), + AFG = wings_color:rgb4bv(wings_pref:get_value(title_text_color)), + PFG0 = wings_color:rgb4bv(wings_pref:get_value(title_passive_text_color, AFG)), + PFG = passive_color(PFG0,AFG), try #win{bar={PBar,_}} = find_win(Prev, Root), _ = wxWindow:getSize(PBar), %% Sync to check PBar validity PChildren = wxWindow:getChildren(PBar), - [wxWindow:setForegroundColour(PChild, TFG) || PChild <- PChildren, - wx:getObjectType(PChild) == wxWindow], + [wxWindow:setForegroundColour(PChild, PFG) || + PChild <- PChildren, + wx:getObjectType(PChild) == wxWindow], wxWindow:setBackgroundColour(PBar, PBG), wxWindow:refresh(PBar) catch _:_ -> ignore @@ -587,8 +622,9 @@ State#state{active=undefined}; #win{bar={ABar,_}} -> AChildren = wxWindow:getChildren(ABar), - [wxWindow:setForegroundColour(AChild, TFG) || AChild <- AChildren, - wx:getObjectType(AChild) == wxWindow], + [wxWindow:setForegroundColour(AChild, AFG) || + AChild <- AChildren, + wx:getObjectType(AChild) == wxWindow], wxWindow:setBackgroundColour(ABar, ABG), wxWindow:refresh(ABar), State#state{active=Name} @@ -596,6 +632,17 @@ State end. +passive_color({R,G,B,A} = PFG0,AFG) -> + if PFG0 =:= AFG -> + if R+G+B < 180 -> %% Dark text + {R+50,G+50,B+50,A}; + true -> + {R-50,G-50,B-50,A} + end; + true -> + PFG0 + end. + update_theme(#state{windows=#{ch:=Root,loose:=Loose}, active=Active}) -> update_theme(Root, Active), [update_theme_0(Win, Active) || Win <- maps:values(Loose)], @@ -617,7 +664,10 @@ WBG = wings_color:rgb4bv(wings_pref:get_value(outliner_geograph_bg)), WChildren = wxWindow:getChildren(Win), - [wxWindow:setBackgroundColour(WChild, WBG) || WChild <- WChildren], + [wxWindow:setBackgroundColour(WChild, WBG) || + Parent <- [Win|WChildren], + WChild <- wxWindow:getChildren(Parent) ++ [Parent], + not wx:is_null(WChild)], case Bar of {TBar,_} -> wxWindow:setBackgroundColour(TBar, TBG); _ -> ignore @@ -664,7 +714,13 @@ ?wxFRAME_FLOAT_ON_PARENT bor ?wxFRAME_NO_TASKBAR bor ?wxNO_BORDER, - Overlay = wxFrame:new(Parent, -1, "", [{style, Flags}]), + Overlay = wxFrame:new(), + case {os:type(), {?wxMAJOR_VERSION, ?wxMINOR_VERSION}} of + {{_, linux}, Ver} when Ver > {3,0} -> + wxFrame:setBackgroundStyle(Overlay, 3); %% ?wxBG_STYLE_TRANSPARENT + _ -> ok + end, + true = wxFrame:create(Overlay, Parent, -1, "", [{style, Flags}]), wxFrame:setBackgroundColour(Overlay, {95,138,255,200}), Overlay. @@ -739,7 +795,7 @@ make(Parent) -> Style = case os:type() of {unix, darwin} -> ?wxSP_3DSASH bor ?wxSP_LIVE_UPDATE; - {win32, _} -> ?wxSP_LIVE_UPDATE; + {win32, _} -> ?wxSP_BORDER bor ?wxSP_LIVE_UPDATE; _ -> ?wxSP_3D bor ?wxSP_LIVE_UPDATE end, New = wxSplitterWindow:new(Parent, [{style, Style}]), @@ -879,11 +935,12 @@ update_win(_, _, _, _) -> false. close_win(Win, #state{windows=#{frame:=TopFrame,ch:=Tree,loose:=Loose,szr:=Szr}=Wins}=State) -> + catch wx_object:stop(Win), case find_win(Win, Tree) of false -> case lists:keyfind(Win, #win.win, maps:values(Loose)) of #win{frame=Frame} = _Win -> - wxMiniFrame:destroy(Frame), + wxFrame:destroy(Frame), _ = wxWindow:findFocus(), %% Sync the destroy State#state{windows=Wins#{loose:=maps:remove(Frame, Loose)}}; false -> @@ -907,6 +964,7 @@ close_window(Delete, Split, Other, GrandP, Szr) -> case GrandP of Split when is_record(Other, win) -> %% TopLevel + wxWindow:hide(Delete), wxWindow:reparent(win(Other), win(GrandP)), wxSplitterWindow:unsplit(win(GrandP), [{toRemove, Delete}]), wxWindow:destroy(Delete), @@ -915,13 +973,15 @@ Split when is_record(Other, split) -> Frame = wxWindow:getParent(win(GrandP)), wxWindow:reparent(win(Other), Frame), - wxSizer:replace(Szr, win(GrandP), win(Other)), + wxSizer:replace(Szr, win(GrandP), win(Other)), + wxWindow:hide(win(GrandP)), wxWindow:destroy(win(GrandP)), _ = wxWindow:findFocus(), %% Sync the destroy {ok, Other}; #split{} -> wxWindow:reparent(win(Other), win(GrandP)), wxSplitterWindow:replaceWindow(win(GrandP), win(Split), win(Other)), + wxWindow:hide(win(Split)), wxWindow:destroy(win(Split)), _ = wxWindow:findFocus(), %% Sync the destroy {ok, Other} @@ -975,9 +1035,13 @@ end, State#{action:=undefined, op:=undefined}; detach_window(#wxMouse{type=left_down, x=X, y=Y}, Frame, #{ch:=Top} = State) -> - #win{bar={Bar,_}} = Win = find_win(Frame, Top), - Pos = wxWindow:clientToScreen(Bar, {X,Y}), - State#{action:=detach_init, op:=#{win=>Win, pos=>Pos}}; + case find_win(Frame, Top) of + false -> + State; + #win{bar={Bar,_}} = Win -> + Pos = wxWindow:clientToScreen(Bar, {X,Y}), + State#{action:=detach_init, op:=#{win=>Win, pos=>Pos}} + end; detach_window(_Ev, _, State) -> %% io:format("Ignore: ~p~n",[_Ev]), State. @@ -1136,12 +1200,13 @@ WinC#win{frame=Win, bar=Wins}. make_bar(Parent, BG, Label, Close) -> - Bar = wxPanel:new(Parent, [{style, ?wxBORDER_SIMPLE}, {size, {-1, ?WIN_BAR_HEIGHT}}]), + Bar = wxPanel:new(Parent, [{style, ?wxBORDER_NONE}, {size, {-1, ?WIN_BAR_HEIGHT}}]), FG = wings_pref:get_value(title_text_color), - #{size:=Sz} = FI = wings_text:get_font_info(?GET(system_font_wx)), + #{size:=Sz} = FI0 = wings_text:get_font_info(?GET(system_font_wx)), + FI = FI0#{size:=Sz-1, weight=>bold}, {Font,Space} = case os:type() of - {unix, darwin} -> {wings_text:make_wxfont(FI#{size:=Sz-1}), 4}; - _ -> {wings_text:make_wxfont(FI#{size:=Sz-2}), 2} + {unix, darwin} -> {wings_text:make_wxfont(FI), 6}; + _ -> {wings_text:make_wxfont(FI), 4} end, wxPanel:setFont(Bar, Font), wxWindow:setBackgroundColour(Bar, BG), @@ -1158,26 +1223,32 @@ {Bar,ST}. make_close_button(Parent, Bar, WBSz, H) -> - Bitmap0 = wxArtProvider:getBitmap("wxART_CROSS_MARK",[{client, "wxART_MESSAGE_BOX"}]), - Bitmap = case os:type() of - {unix, linux} -> - Im0 = wxBitmap:convertToImage(Bitmap0), - Im1 = wxImage:scale(Im0,H,H,[{quality, ?wxIMAGE_QUALITY_HIGH}]), - BM = wxBitmap:new(Im1), - wxImage:destroy(Im1), wxImage:destroy(Im0), - BM; - {_, _} -> %% Do not scale on windows - Bitmap0 - end, - SBM = wxStaticBitmap:new(Bar, ?wxID_EXIT, Bitmap), - %% io:format("SBM = ~p~n",[wxWindow:getSize(SBM)]), - wxSizer:add(WBSz, SBM, [{flag, ?wxALIGN_CENTER}]), Self = self(), CB = fun(_, Ev) -> wxMouseEvent:skip(Ev), Self ! {close_window, Parent} end, - wxWindow:connect(SBM, left_up, [{callback, CB}]). + SBM = case {os:type(), {?wxMAJOR_VERSION, ?wxMINOR_VERSION}} of + {{_, linux}, Ver} when Ver < {3,1} -> + Bitmap = wxArtProvider:getBitmap("wxART_CLOSE", [{size,{16,16}}]), + Butt = wxStaticBitmap:new(Bar, ?wxID_EXIT, Bitmap), + wxWindow:connect(Butt, left_down, [{callback, CB}]), + Butt; + {{win32, _}, Ver} when Ver < {3,1} -> + Bitmap = wxArtProvider:getBitmap("wxART_CLOSE", []), + Butt = wxStaticBitmap:new(Bar, ?wxID_EXIT, Bitmap), + wxWindow:connect(Butt, left_down, [{callback, CB}]), + Butt; + {_, _} -> + Bitmap = wxArtProvider:getBitmap("wxART_CLOSE", []), + Butt = wxBitmapButton:new(Bar, ?wxID_EXIT, Bitmap, + [{size, {H,H}}, {style,?wxNO_BORDER}]), + wxWindow:connect(Butt, command_button_clicked, [{callback, CB}]), + Butt + end, + %% io:format("SBM = ~p~n",[wxWindow:getSize(SBM)]), + wxSizer:add(WBSz, SBM, [{flag, ?wxALIGN_CENTER}]), + wxWindow:connect(SBM, command_button_clicked, [{callback, CB}]). export_loose(Windows) -> Exp = fun(#win{name=Name, win=Win, frame=Frame}) -> @@ -1274,8 +1345,20 @@ init_menubar(Frame) -> ets:new(wings_menus, [named_table, public, {keypos,2}]), put(wm_active, {menubar, geom}), + WorkAround = try + %% Only exists in future wx (erlang release) + %% and fool dialyzer + WxMB = list_to_atom("wxMenuBar"), + WxMB:setAutoWindowMenu(false), + false + catch _:_ -> + case os:type() of + {_, darwin} -> true; + _ -> false + end + end, MB = wxMenuBar:new(), - wings_menu:setup_menus(MB, top_menus()), + wings_menu:setup_menus(MB, top_menus(WorkAround)), wxFrame:setMenuBar(Frame, MB), erase(wm_active), MB. diff -Nru wings3d-2.2.4/src/wings_geom_win.erl wings3d-2.2.5/src/wings_geom_win.erl --- wings3d-2.2.4/src/wings_geom_win.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_geom_win.erl 2019-12-07 15:15:57.000000000 +0000 @@ -465,7 +465,8 @@ wxTreeCtrl:setForegroundColour(TC, FG), wxTreeCtrl:assignImageList(TC, load_icons()), - LCStyle = ?wxLC_REPORT bor ?wxLC_NO_HEADER bor ?wxLC_EDIT_LABELS bor ?wxLC_SINGLE_SEL, + LCStyle = ?wxLC_REPORT bor ?wxLC_NO_HEADER bor ?wxLC_EDIT_LABELS + bor ?wxLC_SINGLE_SEL bor wings_frame:get_border(), LC = wxListCtrl:new(Splitter, [{style, LCStyle}]), wxListCtrl:setBackgroundColour(LC, BG), wxListCtrl:setForegroundColour(LC, FG), diff -Nru wings3d-2.2.4/src/wings_gl.erl wings3d-2.2.5/src/wings_gl.erl --- wings3d-2.2.4/src/wings_gl.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_gl.erl 2019-12-07 15:15:57.000000000 +0000 @@ -71,7 +71,7 @@ }. window(Parent, Context, Connect, Show) -> - Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxWANTS_CHARS, + Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxWANTS_CHARS bor wings_frame:get_border(), Flags = [attributes(), {style, Style}], GL = case Context of undefined -> diff -Nru wings3d-2.2.4/src/wings_glfont.erl wings3d-2.2.5/src/wings_glfont.erl --- wings3d-2.2.4/src/wings_glfont.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_glfont.erl 2019-12-07 15:15:57.000000000 +0000 @@ -100,7 +100,7 @@ render(#font{} = GLFont, String) when is_list(String) -> render_text(GLFont, String); render(#font{}, {_, _, <<>>}) -> ok; -render(#font{tex=TexId, height=H}, {X,Y, Bin0}) -> +render(#font{tex=TexId, height=H}, {X,Y, _, Bin0}) -> Size = byte_size(Bin0), Bin = <<_:2/unit:32, TxBin/bytes>> = if Size < ?BIN_XTRA -> <>; @@ -141,8 +141,8 @@ %% gl:enableClientState(?GL_TEXTURE_COORD_ARRAY),
%% gl:drawArrays(?GL_QUADS, 0, (byte_size(Bin)-?BIN_XTRA) div 16),
render_to_binary(#font{glyphs=Gs, height=H, ih=IH, iw=IW}, - String, Data = {X,Y,D}) - when is_integer(X), is_integer(Y), is_binary(D) -> + String, Data = {X,Y,XS,D}) + when is_integer(X), is_integer(Y), is_integer(XS), is_binary(D) -> render_text3(String, Gs, IH, IW, H, Data). %%-------------------------------------------------------------------- @@ -267,17 +267,17 @@ %% gl:pixelStorei(?GL_UNPACK_ALIGNMENT, 1), Mode = proplists:get_value(tex_mode, Options, ?GL_MODULATE), - gl:texEnvf(?GL_TEXTURE_ENV, ?GL_TEXTURE_ENV_MODE, Mode), + gl:texEnvi(?GL_TEXTURE_ENV, ?GL_TEXTURE_ENV_MODE, Mode), MinF = proplists:get_value(tex_min, Options, ?GL_LINEAR), - gl:texParameterf(?GL_TEXTURE_2D,?GL_TEXTURE_MIN_FILTER,MinF), + gl:texParameteri(?GL_TEXTURE_2D,?GL_TEXTURE_MIN_FILTER, MinF), MagF = proplists:get_value(tex_mag, Options, ?GL_LINEAR), - gl:texParameterf(?GL_TEXTURE_2D,?GL_TEXTURE_MAG_FILTER,MagF), + gl:texParameteri(?GL_TEXTURE_2D,?GL_TEXTURE_MAG_FILTER,MagF), WS = proplists:get_value(tex_wrap_s, Options, ?GL_CLAMP), - gl:texParameterf(?GL_TEXTURE_2D,?GL_TEXTURE_WRAP_S, WS), + gl:texParameteri(?GL_TEXTURE_2D,?GL_TEXTURE_WRAP_S, WS), WT = proplists:get_value(tex_wrap_t, Options, ?GL_CLAMP), - gl:texParameterf(?GL_TEXTURE_2D,?GL_TEXTURE_WRAP_T, WT), + gl:texParameteri(?GL_TEXTURE_2D,?GL_TEXTURE_WRAP_T, WT), GEN_MM = proplists:get_value(tex_gen_mipmap, Options, ?GL_FALSE), gl:texParameteri(?GL_TEXTURE_2D, ?GL_GENERATE_MIPMAP, GEN_MM), @@ -300,13 +300,12 @@ MDC. render_text(Font=#font{glyphs=Gs, height=H, ih=IH, iw=IW}, String) -> - Res = render_text3(String, Gs, IH, IW, H, {0,0, <<>>}), + Res = render_text3(String, Gs, IH, IW, H, {0,0,0, <<>>}), render(Font, Res). -render_text3([$\n|String], Gs, IH, IW, H, Data0) -> +render_text3([$\n|String], Gs, IH, IW, H, {_,H0,XS,Bin}) -> %% New line - {_,H0,Bin} = Data0, - render_text3(String, Gs, IH, IW, H, {0, H0+H, Bin}); + render_text3(String, Gs, IH, IW, H, {XS,H0+H,XS,Bin}); render_text3([$\t|String], Gs, IH, IW, H, Data0) -> %% Tab [{_,Space}] = ets:lookup(Gs, 32), @@ -327,15 +326,15 @@ render_text3(String, Gs, IH, IW, H, Data); render_text3(Bin, Gs, IH, IW, H, Data) when is_binary(Bin) -> render_text3(unicode:characters_to_list(Bin), Gs, IH, IW, H, Data); -render_text3([], _Gs, _IH, _IW, _H, {W,H0,Bin}) -> - {W, H0, Bin}. +render_text3([], _Gs, _IH, _IW, _H, Res) -> + Res. -render_glyph(#glyph{u=U,v=V,w=W,h=H},IW,IH, {X0,Y0,Bin}) -> +render_glyph(#glyph{u=U,v=V,w=W,h=H},IW,IH, {X0,Y0,XS,Bin}) -> X1 = X0 + W, UD = U + W*IW, VD = V + H*IH, YH = Y0 - H, - {X1,Y0, + {X1,Y0,XS, < - Panel = wxPanel:new(Parent), - Szr = wxBoxSizer:new(?wxVERTICAL), - {_, _Sz, Img} = lists:keyfind(about_wings, 1, Imgs), - Center = [{flag, ?wxALIGN_CENTER bor ?wxALL}, {border, 15}], - Right = [{flag, ?wxALIGN_RIGHT bor ?wxRIGHT}, {border, 15}], - wxSizer:add(Szr, wxStaticBitmap:new(Panel, ?wxID_ANY, wxBitmap:new(Img)), Center), - Add = fun({spacer, _, H}) -> wxSizer:addSpacer(Szr, H); - ({text, String}) -> wxSizer:add(Szr, wxStaticText:new(Panel, ?wxID_ANY, String), Right) - end, - wx:foreach(Add, splash_contents()), - wxSizer:addSpacer(Szr, 15), - wxPanel:setSizer(Panel, Szr), - {Panel, Szr}. + %% the border shows a nice splash window when Wings3d starts + Panel = wxPanel:new(Parent,[{style,?wxBORDER_SIMPLE}]), + + {_, {W,H}, Img} = lists:keyfind(about_wings, 1, Imgs), + P = fun(Sz) -> {proportion, Sz} end, + B = fun(Sz) -> {border,Sz} end, + SzrMain = wxBoxSizer:new(?wxVERTICAL), + TopPanel = wxPanel:new(Panel, [{size,{W,H}}]), + wxSizer:add(SzrMain, TopPanel, [P(0)]), + + ImageFile = filename:join([wings_util:lib_dir(wings), "textures", "about_wings_art.png"]), + Image = wxImage:new(ImageFile), + BMImage = wxBitmap:new(Image), + wxSizer:add(SzrMain, wxStaticBitmap:new(Panel, ?wxID_ANY, BMImage), + [P(0), B(3), {flag, ?wxALL bor ?wxALIGN_CENTER}]), + wxImage:destroy(Image), + wxBitmap:destroy(BMImage), + + Font = wxFont:new(10, ?wxDEFAULT, ?wxNORMAL, ?wxFONTWEIGHT_NORMAL), + wxWindow:setFont(Panel, Font), + wxFont:destroy(Font), + SzrBottomCol = wxBoxSizer:new(?wxHORIZONTAL), + Left = wxStaticText:new(Panel, ?wxID_ANY, splash_content(bottom_left), []), + wxSizer:add(SzrBottomCol, Left, [P(0), B(5), {flag, ?wxLEFT}]), + wxSizer:add(SzrBottomCol, 0, 0, [P(1), {flag, ?wxEXPAND}]), + Right = wxStaticText:new(Panel, ?wxID_ANY, splash_content(bottom_right), + [{style, ?wxALIGN_RIGHT}]), + wxSizer:add(SzrBottomCol, Right, [P(0), B(5), {flag, ?wxRIGHT}]), + + wxSizer:add(SzrMain, SzrBottomCol, [P(1), B(10), {flag,?wxALL bor ?wxEXPAND}]), + wxPanel:setSizer(Panel, SzrMain), + wxPanel:layout(Panel), + %% used to allow us to draw the header text over the StaticBitmap + wxWindow:connect(TopPanel, paint, [{callback, fun redraw/2}, {userData,Img}]), + {Panel, SzrMain}. + +-define(lblVerX, 180). +-define(lblVerY, 27). +-define(lblDesX, 48). +redraw(#wx{obj=TopPanel, userData=Img}, Obj) -> + wxEvent:skip(Obj), + String = splash_content(top), + Www = "www.wings3d.com", + {MaxW,MaxH} = wxWindow:getSize(TopPanel), + DC = case os:type() of + {win32, _} -> %% Flicker on windows + wx:typeCast(wxBufferedPaintDC:new(TopPanel), wxPaintDC); + _ -> + wxPaintDC:new(TopPanel) + end, + + GC = wxGraphicsContext:create(DC), + %% Drawing the top image background which will get the Wings3D's version stamped over + BM = wxBitmap:new(Img), + wxGraphicsContext:drawBitmap(GC, BM, 0, 0, wxBitmap:getWidth(BM), wxBitmap:getHeight(BM)), + wxBitmap:destroy(BM), + + %% Prepating to use transparent color for the text background + BGB = wxBrush:new({0,0,0}, [{style, ?wxTRANSPARENT}]), + wxGraphicsContext:setBrush(GC, BGB), + + %% Drawing the version number + Font1 = wxFont:new(11, ?wxDEFAULT, ?wxNORMAL, ?wxFONTWEIGHT_BOLD), + wxGraphicsContext:setFont(GC, Font1, {200,200,200}), + {_,H0,_,_} = wxGraphicsContext:getTextExtent(GC, "v2"), + wxGraphicsContext:drawText(GC, "v" ++ ?WINGS_VERSION, ?lblVerX, ?lblVerY-H0), + wxFont:destroy(Font1), + + %% Drawing Wings3D description + Font2 = wxFont:new(8, ?wxDEFAULT, ?wxNORMAL, ?wxFONTWEIGHT_NORMAL), + wxGraphicsContext:setFont(GC, Font2, {200,200,200}), + {W,H,_,_} = wxGraphicsContext:getTextExtent(GC, Www), + wxGraphicsContext:drawText(GC, String, ?lblDesX, MaxH-H-3), + wxGraphicsContext:drawText(GC, Www, MaxW-W-5, MaxH-H-3), + wxFont:destroy(Font2), + + wxBrush:destroy(BGB), + wxGraphicsContext:destroy(GC), + wxPaintDC:destroy(DC), + ok. + +splash_content(top) -> + [Top,_,_] = splash_contents(), + Top; +splash_content(bottom_left) -> + [_,Left,_] = splash_contents(), + Left; +splash_content(bottom_right) -> + [_,_,Right] = splash_contents(), + Right. splash_contents() -> - [{text, ?WINGS_VERSION}, - {spacer,0,10}, - {text,?__(1,"Wings 3D is a subdivision modeler inspired")}, - {text,?__(2,"by Nendo and Mirai from IZware.")}, - {spacer,0,10}, - {text,?__(3,"Wings 3D comes with absolutely no warranty,")}, - {text,?__(4,"but is completely free for any kind of use")}, - {text,?__(5,"(including commercial).")}, - {spacer,0,10}, - {text,?__(6,"Copyright") ++ [$\s,169] ++ " 2001-2016 Björn Gustavsson "}, - {text,"Dan Gudmundsson" ++ ?__(7," and Others")} + [?__(1,"Wings 3D is a subdivision modeler inspired") ++ " " ++ + ?__(2,"by Nendo and Mirai from IZware."), + + ?__(3,"Wings 3D comes with absolutely no warranty,") ++ "\n" ++ + ?__(4,"but is completely free for any kind of use") ++ "\n" ++ + ?__(5,"(including commercial)."), + + ?__(6,"Copyright") ++ [$\s,169] ++ " 2001-2019 Björn Gustavsson" ++ "\n" ++ + "Dan Gudmundsson" ++ ?__(7," and Others") ]. edit_prefs() -> diff -Nru wings3d-2.2.4/src/wings_image.erl wings3d-2.2.5/src/wings_image.erl --- wings3d-2.2.4/src/wings_image.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_image.erl 2019-12-07 15:15:57.000000000 +0000 @@ -196,7 +196,9 @@ viewport_screenshot(Name) -> %% screenshot of just the viewport scene - {X,Y,W,H} = wings_wm:viewport(), + {X,Y,W0,H0} = wings_wm:viewport(), + Scale = wings_wm:win_scale(), + W = round(W0*Scale), H = round(H0*Scale), gl:pixelStorei(?GL_PACK_ALIGNMENT, 1), gl:readBuffer(?GL_FRONT), Mem = wings_io:get_buffer(W*H*3, ?GL_UNSIGNED_BYTE), @@ -417,8 +419,7 @@ ?CHECK_ERROR(), TxId. -do_update(Id, In = #e3d_image{width=W,height=H,type=Type,name=NewName}, - #ist{images=Images0}=S) -> +do_update(Id, In = #e3d_image{name=NewName}, #ist{images=Images0}=S) -> #img{e3d=Im0, partof=Combs} = Image = gb_trees:get(Id, Images0), %% Cleanup combined textures and recreate (later) on the fly (can be optimized) Ids = [erase(Comb)|| Comb <- Combs], @@ -432,15 +433,7 @@ Im = maybe_convert(In#e3d_image{filename=File, name=Name}), TxId = get(Id), Images = gb_trees:update(Id, Image#img{e3d=Im, partof=[]}, Images1), - Size = {Im0#e3d_image.width, Im0#e3d_image.height, Im0#e3d_image.type}, - case Size of - {W,H,Type} -> - {Format, TexType} = texture_format(Im), - gl:bindTexture(?GL_TEXTURE_2D, TxId), - gl:texSubImage2D(?GL_TEXTURE_2D, 0, 0, 0, W, H, Format, TexType, Im#e3d_image.image); - _ -> - init_texture_1(Im, TxId) - end, + init_texture_1(Im, TxId), case get({Id,normal}) of undefined -> S#ist{images=Images}; diff -Nru wings3d-2.2.4/src/wings_io.erl wings3d-2.2.5/src/wings_io.erl --- wings3d-2.2.4/src/wings_io.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_io.erl 2019-12-07 15:15:57.000000000 +0000 @@ -229,10 +229,10 @@ set_color(Color), N = info_lines(Info), {W,_} = wings_wm:win_size(), - gl:recti(X, Y, W, Y + N*?LINE_HEIGHT + 2) + gl:recti(X, Y, W, Y + N*?LINE_HEIGHT + 6) end), set_color(wings_pref:get_value(info_color)), - text_at(X + 4, Y + ?CHAR_HEIGHT, Info). + text_at(X + 5, Y + ?CHAR_HEIGHT+3, Info). info_lines(Info) -> info_lines_1(Info, 1). diff -Nru wings3d-2.2.4/src/wings_light.erl wings3d-2.2.5/src/wings_light.erl --- wings3d-2.2.4/src/wings_light.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_light.erl 2019-12-07 15:15:57.000000000 +0000 @@ -60,9 +60,16 @@ AreaMatTxId = load_area_light_tab(LTCmat), DefEnvMap = "grandcanyon.png", EnvImgRec = wings_image:image_read([{filename, filename:join(Path, DefEnvMap)}]), - EnvIds = case cl_setup(Recompile) of - {error, _} -> fake_envmap(Path, EnvImgRec); - CL -> make_envmap(CL, EnvImgRec) + EnvIds = case wings:is_fast_start() orelse cl_setup(Recompile) of + true -> + fake_envmap(Path, EnvImgRec); + {error, _} -> + ErrorStr = ?__(1, "Could not initialize OpenCL: env lighting limited ~n"), + io:format(ErrorStr,[]), + wings_status:message(geom, ErrorStr), + fake_envmap(Path, EnvImgRec); + CL -> + make_envmap(CL, EnvImgRec) end, [?SET(Tag, Id) || {Tag,Id} <- [AreaMatTxId|EnvIds]], init_opengl(), @@ -284,14 +291,17 @@ edit(Id, St) -> Obj = wings_obj:get(Id, St), - {_, Prop} = get_light(Obj, false, St), case Obj of #{light:=#light{type=ambient}} -> + {_, Prop} = get_light(Obj, false, St), {dialog,Qs,Fun} = edit_ambient_dialog(Obj, Prop, St), wings_dialog:dialog(?__(2,"Ambient Light Properties"), Qs, Fun); #{light:=#light{}} -> + {_, Prop} = get_light(Obj, false, St), {dialog,Qs,Fun} = edit_dialog(Obj, Prop, St), - wings_dialog:dialog(?__(3,"Light Properties"), Qs, Fun) + wings_dialog:dialog(?__(3,"Light Properties"), Qs, Fun); + _ -> + wings_u:error_msg(?__(4,"Select one area light.")) end. edit_ambient_dialog(Obj, Prop0, St) -> @@ -923,9 +933,6 @@ {areamatrix_tex, ImId}. fake_envmap(Path, EnvImgRec) -> - ErrorStr = ?__(1, "Could not initialize OpenCL: env lighting limited ~n"), - io:format(ErrorStr,[]), - wings_status:message(geom, ErrorStr), %% Poor mans version with blured images SpecBG = wings_image:e3d_to_wxImage(EnvImgRec), wxImage:rescale(SpecBG, 512, 256, [{quality, ?wxIMAGE_QUALITY_HIGH}]), diff -Nru wings3d-2.2.4/src/wings_material.erl wings3d-2.2.5/src/wings_material.erl --- wings3d-2.2.4/src/wings_material.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_material.erl 2019-12-07 15:15:57.000000000 +0000 @@ -778,8 +778,9 @@ mat_preview(Canvas, Common, Vbo, Maps) -> {W,H} = wxWindow:getSize(Canvas), + Scale = wxWindow:getContentScaleFactor(Canvas), gl:pushAttrib(?GL_ALL_ATTRIB_BITS), - gl:viewport(0, 0, W, H), + gl:viewport(0, 0, round(W*Scale), round(H*Scale)), {BR,BG,BB, _} = wxWindow:getBackgroundColour(wxWindow:getParent(Canvas)), %% wxSystemSettings:getColour(?wxSYS_COLOUR_BACKGROUND), BGC = fun(Col) -> (Col-15) / 255 end, diff -Nru wings3d-2.2.4/src/wings_menu.erl wings3d-2.2.5/src/wings_menu.erl --- wings3d-2.2.4/src/wings_menu.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_menu.erl 2019-12-07 15:15:57.000000000 +0000 @@ -161,11 +161,19 @@ have_magnet(Ps, _) -> proplists:is_defined(magnet, Ps). +mac_fix() -> + %% Mac wxWidgets-3.1.3 capture_mouse don't work as expected + %% Try to work around it. + case os:type() of + {unix, darwin} -> true; + _ -> false + end. wx_popup_menu_init(Parent,GlobalPos,Names,Menus0) -> + mac_fix() andalso wings_wm:grab_focus(), Owner = wings_wm:this(), - Entries = wx_popup_menu(Parent,GlobalPos,Names,Menus0,false,Owner), - {push, fun(Ev) -> popup_event_handler(Ev, {Parent,Owner}, Entries) end}. + {Pid, Entries} = wx_popup_menu(Parent,GlobalPos,Names,Menus0,false,Owner), + {push, fun(Ev) -> popup_event_handler(Ev, {Parent,Owner,Pid}, Entries) end}. wx_popup_menu(Parent,Pos,Names,Menus0,Magnet,Owner) -> HotKeys = wings_hotkey:matching(Names), @@ -182,30 +190,40 @@ MEs0 = [ME#menu{name=undefined} || ME <- Entries], CreateMenu = fun() -> setup_dialog(Parent, MEs0, Magnet, Pos) end, Env = wx:get_env(), - spawn_link(fun() -> + Pid = spawn_link(fun() -> try wx:set_env(Env), - {Dialog, Panel, MEs} = wx:batch(CreateMenu), - popup_events(Dialog, Panel, MEs, Magnet, undefined, Names, Owner), - wxPopupTransientWindow:destroy(Dialog) + {Frame, Panel, MEs, Cols} = wx:batch(CreateMenu), + popup_events(Frame, Panel, MEs, Cols, Magnet, undefined, Names, Owner), + wxWindow:releaseMouse(Panel), + wxWindow:hide(Frame), + wxFrame:destroy(Frame) catch _:Reason -> io:format("CRASH ~p ~p~n",[Reason, erlang:get_stacktrace()]) end, normal end), - Entries. + {Pid,Entries}. setup_dialog(Parent, Entries0, Magnet, {X0,Y0}=ScreenPos) -> X = X0-20, Y1 = Y0-10, - Dialog = wxPopupTransientWindow:new(Parent, [{style, ?wxBORDER_SIMPLE}]), - wxWindow:setExtraStyle(Dialog, ?wxWS_EX_PROCESS_IDLE), - Panel = wxPanel:new(Dialog), - wxPanel:setFont(Panel, ?GET(system_font_wx)), - %% wxPanel:setBackgroundStyle(Panel, ?wxBG_STYLE_TRANSPARENT), - Cols = {BG,_FG} = {colorB(menu_color),colorB(menu_text)}, - wxWindow:setBackgroundColour(Panel, BG), - wxWindow:setBackgroundColour(Dialog, BG), + Flags = ?wxFRAME_TOOL_WINDOW bor ?wxFRAME_FLOAT_ON_PARENT bor ?wxFRAME_NO_TASKBAR, + Frame = wxFrame:new(), + wxWindow:setExtraStyle(Frame, ?wxWS_EX_PROCESS_IDLE bor ?wxFRAME_EX_METAL), + %% case {os:type(), {?wxMAJOR_VERSION, ?wxMINOR_VERSION}} of + %% {{_, linux}, Ver} when Ver > {3,0} -> + %% wxFrame:setBackgroundStyle(Frame, 3); %% ?wxBG_STYLE_TRANSPARENT + %% _ -> ok + %% end, + true = wxFrame:create(Frame, Parent, -1, "", [{style, Flags}]), + Panel = wxWindow:new(Frame, -1, [{style, ?wxWANTS_CHARS}]), + wxWindow:setFont(Panel, ?GET(system_font_wx)), + {{R,G,B,A},FG} = {colorB(menu_color),colorB(menu_text)}, + Cols = {{R,G,B,A}, FG}, + catch wxFrame:setTransparent(Frame, 240), + wxWindow:setBackgroundColour(Frame, {R,G,B, 240}), + wxWindow:setBackgroundColour(Panel, {R,G,B, 240}), Main = wxBoxSizer:new(?wxHORIZONTAL), Sizer = wxBoxSizer:new(?wxVERTICAL), MinHSzs = calc_min_sizes(Entries0, Panel, 5, 5), @@ -216,37 +234,100 @@ wxSizer:addSpacer(Main, 5), wxPanel:setSizer(Panel, Main), wxSizer:fit(Main, Panel), - wxPopupTransientWindow:setClientSize(Dialog, wxWindow:getSize(Panel)), - wxPopupTransientWindow:connect(Dialog, show), + wxWindow:setClientSize(Frame, wxWindow:getSize(Panel)), + wxWindow:connect(Frame, show), {_, MaxH} = wx_misc:displaySize(), - {_,H} = wxPopupTransientWindow:getSize(Dialog), + {_,H} = wxWindow:getSize(Frame), Y = if ((Y1+H) > MaxH) -> max(0, (MaxH-H-5)); - true -> Y1 + true -> Y1 end, - menu_connect([Panel], [left_up, middle_up, right_up]), - wxPopupTransientWindow:position(Dialog, {X,Y}, {0,0}), - wxPopupTransientWindow:connect(Panel, char_hook, [{skip, true}]), - wxPopupTransientWindow:popup(Dialog), - wxPanel:setFocusIgnoringChildren(Panel), + [wxWindow:connect(Panel, Ev, [{skip, false}]) || + Ev <- [motion, left_up, middle_up, right_up]], + wxWindow:move(Frame, {X,Y}), + wxPanel:connect(Panel, char), + wxPanel:connect(Panel, char_hook), + catch wxWindow:connect(Panel, mouse_capture_lost), %% Not available in old wx's. + wxFrame:show(Frame), + %% Color active menuitem {MX, MY} = wxWindow:screenToClient(Panel, ScreenPos), - case find_active_panel(Panel, MX, MY) of - {false,_} -> ignore; - {ActId, ActPanel} -> - self() ! #wx{id=ActId, obj= ActPanel, - event=#wxMouse{type=enter_window,x=0,y=0, - leftDown=false,middleDown=false,rightDown=false, - controlDown=false,shiftDown=false,altDown=false,metaDown=false, - wheelRotation=0, wheelDelta=0, linesPerAction=0 - }} + MEv = #wxMouse{type=motion,x=MX,y=MY, + leftDown=false,middleDown=false,rightDown=false, + controlDown=false,shiftDown=false,altDown=false,metaDown=false, + wheelRotation=0, wheelDelta=0, linesPerAction=0 + }, + self() ! #wx{id=-1, obj=wx:null(), event=MEv}, + {Frame, Panel, Entries, Cols}. + +popup_events(Frame, Panel, Entries, Cols, Magnet, Previous, Ns, Owner) -> + receive + #wx{event=#wxMouse{x=X,y=Y,type=motion}} -> + Do = fun() -> + case find_active_panel(Panel, X, Y) of + {Id, Obj} when is_number(Id), Obj =/= Previous -> + wings_status:message(Owner, entry_msg(Id, Entries), ""), + {BG,FG} = Cols, + setup_colors(Previous, BG, FG), + setup_colors(Obj, colorB(menu_hilite),colorB(menu_hilited_text)), + Obj; + _ -> + Previous + end + end, + Line = wx:batch(Do), + popup_events(Frame, Panel, Entries, Cols, Magnet, Line, Ns, Owner); + #wx{event=Ev=#wxMouse{y=Y, x=X}} -> + What = mouse_button(Ev), + case find_active_panel(Panel, X, Y) of + {false, outside} when What =:= right_up -> + Pos = wxWindow:clientToScreen(Frame,{X,Y}), + wxWindow:move(Frame, Pos), + wings_wm:psend(Owner, redraw), + popup_events(Frame, Panel, Entries, Cols, Magnet, Previous, Ns, Owner); + {false, outside} -> + wings_wm:psend(Owner, cancel); + {false, inside} -> + popup_events(Frame, Panel, Entries, Cols, Magnet, Previous, Ns, Owner); + {PanelId, RowPanel} -> + {PX,_} = wxWindow:getPosition(RowPanel), + Id = get_hit_id(X-PX, RowPanel, PanelId), + MagnetClick = Magnet orelse + magnet_pressed(wings_msg:free_rmb_modifier(), Ev), + wings_wm:psend(Owner, {click, Id, {What, MagnetClick}, Ns}) + end; + #wx{event=#wxShow{show=true}} -> + capture_mouse(Frame, Panel), + popup_events(Frame, Panel, Entries, Cols, Magnet, Previous, Ns, Owner); + #wx{event=#wxKey{keyCode=Key}} = _Ev -> + if Key =:= ?WXK_ESCAPE -> + wings_wm:psend(Owner, cancel); + true -> + popup_events(Frame, Panel, Entries, Cols, Magnet, Previous, Ns, Owner) + end; + cancel -> + wings_wm:psend(Owner, cancel); + #wx{event=#wxMouseCaptureLost{}} -> + wings_wm:psend(Owner, cancel); + _Ev -> + ?dbg("Got Ev ~p ~n", [_Ev]), + popup_events(Frame, Panel, Entries, Cols, Magnet, Previous, Ns, Owner) + end. + +capture_mouse(Frame, Panel) -> + wxWindow:disconnect(Frame, show), + case os:type() of %% Capture mouse on GTK must be done on an realized window + {unix, linux} -> timer:sleep(150); %% sleep so we ensure that (async) creation is done + _ -> ok end, - {Dialog, Panel, Entries}. + wxWindow:setFocus(Panel), + wxWindow:captureMouse(Panel), + ok. %% If the mouse is not moved after popping up the menu, the meny entry %% is not active, find_active_panel finds the active row. find_active_panel(Panel, MX, MY) -> - {_,_,WinWidth,_} = wxWindow:getRect(Panel), - case MX > 0 andalso MX < WinWidth of + {_,_,WinW,WinH} = wxWindow:getRect(Panel), + case MX > 0 andalso MX < WinW andalso MY > 0 andalso MY < WinH of true -> find_active_panel_1(Panel, MY); false -> {false, outside} end. @@ -275,54 +356,25 @@ Found end. -popup_events(Dialog, Panel, Entries, Magnet, Previous, Ns, Owner) -> - receive - #wx{id=Id, obj=Obj, event=#wxMouse{type=enter_window}} -> - Set = fun() -> - setup_colors(Previous, colorB(menu_color), colorB(menu_text)), - setup_colors(Obj, colorB(menu_hilite),colorB(menu_hilited_text)) - end, - Line = wx:batch(Set), - wings_status:message(Owner, entry_msg(Id, Entries), ""), - popup_events(Dialog, Panel, Entries, Magnet, Line, Ns, Owner); - #wx{id=Id0, event=Ev=#wxMouse{y=Y, x=X}} -> - Id = case Id0 > 0 orelse find_active_panel(Panel, X, Y) of - true -> Id0; - {false, _} = No -> No; - {AId, _} -> AId - end, - What = mouse_button(Ev), - case Id of - {false, outside} when What =:= right_up -> - Pos = wxWindow:clientToScreen(Dialog,{X,Y}), - wxPopupTransientWindow:position(Dialog, Pos, {0,0}), - wings_wm:psend(Owner, redraw), - popup_events(Dialog, Panel, Entries, Magnet, Previous, Ns, Owner); - {false, _} -> - popup_events(Dialog, Panel, Entries, Magnet, Previous, Ns, Owner); - _Integer -> - wxPopupTransientWindow:dismiss(Dialog), - MagnetClick = Magnet orelse - magnet_pressed(wings_msg:free_rmb_modifier(), Ev), - wings_wm:psend(Owner, {click, Id, {What, MagnetClick}, Ns}) - end; - #wx{event=#wxShow{}} -> - case wxTopLevelWindow:isShown(Dialog) of - false -> - wings_wm:psend(Owner, cancel); - true -> - popup_events(Dialog, Panel, Entries, Magnet, Previous, Ns, Owner) - end; - #wx{event=#wxKey{keyCode=Key}} -> - if Key =:= ?WXK_ESCAPE -> - wxPopupTransientWindow:dismiss(Dialog), - wings_wm:psend(Owner, cancel); - true -> - popup_events(Dialog, Panel, Entries, Magnet, Previous, Ns, Owner) - end; - _Ev -> - io:format("Got Ev ~p ~n", [_Ev]), - popup_events(Dialog, Panel, Entries, Magnet, Previous, Ns, Owner) +get_hit_id(MX, Panel, Id0) -> + Sizer = wxWindow:getSizer(Panel), + Children = wxSizer:getChildren(Sizer), + Check = fun(Item) -> + {X, _, W, _} = wxSizerItem:getRect(Item), + if MX < X -> false; + MX >= (X+W) -> false; + true -> + Active = wxSizerItem:getWindow(Item), + case wx:is_null(Active) orelse wxWindow:getId(Active) of + true -> false; + Id when Id < 0 -> false; + Id -> {true, Id} + end + end + end, + case lists:filtermap(Check, Children) of + [] -> Id0; + [Id|_] -> Id end. magnet_pressed(?CTRL_BITS, #wxMouse{controlDown=true}) -> true; @@ -351,20 +403,32 @@ end. popup_event_handler(cancel, _, _) -> + mac_fix() andalso wings_wm:release_focus(), pop; -popup_event_handler({click, Id, Click, Ns}, {Parent,Owner}=Own, Entries0) -> +popup_event_handler({click, Id, Click, Ns}, {Parent,Owner,_Pid}, Entries0) -> case popup_result(lists:keyfind(Id, 2, Entries0), Click, Ns, Owner) of - pop -> pop; + pop -> + mac_fix() andalso wings_wm:release_focus(), + pop; {submenu, Names, Menus, MagnetClick} -> {_, X0, Y0} = wings_io:get_mouse_state(), Pos = wxWindow:screenToClient(wings_wm:this_win(), {X0,Y0}), {X,Y} = wxWindow:clientToScreen(wings_wm:this_win(), Pos), - Entries = wx_popup_menu(Parent, {X,Y}, Names, Menus, MagnetClick, Owner), - {replace, fun(Ev) -> popup_event_handler(Ev, Own, Entries) end} + {Pid,Entries} = wx_popup_menu(Parent, {X,Y}, Names, Menus, MagnetClick, Owner), + {replace, fun(Ev) -> popup_event_handler(Ev, {Parent,Owner,Pid}, Entries) end} end; popup_event_handler(redraw,_,_) -> defer; -popup_event_handler(#mousemotion{},_,_) -> defer; +popup_event_handler(#mousemotion{},_,_) -> + keep; +popup_event_handler(#keyboard{sym=?SDLK_ESCAPE}, {_, _, Pid}, _) -> + Pid ! cancel, %% Keyboard focus fails on mac wxWidgets-3.1.3 + keep; +popup_event_handler(lost_focus, {_, _, Pid}, _) -> + %% just focus lost + %% mac wxWidgets capture mouse outside window does not get mouse click + mac_fix() andalso (Pid ! cancel), + keep; popup_event_handler(_Ev,_,_) -> %% io:format("Hmm ~p ~n",[_Ev]), keep. @@ -465,7 +529,6 @@ [wxWindow:setToolTip(Win, wxToolTip:new(TipMsg)) || Win <- Controls], wxPanel:setSizerAndFit(Panel, Line), wxSizer:add(Sizer, Panel, [{flag, ?wxEXPAND},{proportion, 1}]), - menu_connect(Controls, [left_up, middle_up, right_up, enter_window]), setup_popup(Es, Sizer, Sz, Cs, Parent, Magnet, [ME#menu{help=CmdMsg}|Acc]); setup_popup([#menu{type=menu, wxid=Id, desc=Desc, help=Help, opts=Props, hk=HK}=ME|Es], Sizer, Sz = {Sz1,Sz2}, Cs, Parent, Magnet, Acc) -> @@ -504,7 +567,6 @@ [wxWindow:setToolTip(Win, wxToolTip:new(TipMsg)) || Win <- [Panel,T1,T2|BM]], wxPanel:setSizerAndFit(Panel, Line), wxSizer:add(Sizer, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]), - menu_connect([Panel,T1,T2|BM], [left_up, middle_up, right_up, enter_window]), Win = #{panel=>Panel, label=>T1, hotkey=>T2}, Pop = ME#menu{wxid=Id, help=CmdMsg, object=Win}, setup_popup(Es, Sizer, Sz, Cs, Parent, Magnet, [Pop|Acc]); @@ -547,9 +609,6 @@ set_entry_id(Id, ME) -> ME#menu{wxid=Id}. -menu_connect(Windows, Evs) -> - [ [wxWindow:connect(Win, Ev) || Ev <- Evs] || Win <- Windows]. - setup_colors(Windows, {BG, FG}) -> setup_colors(Windows, BG, FG). @@ -562,7 +621,7 @@ case wx:getObjectType(Window) of wxPanel -> setup_colors([Window|wxWindow:getChildren(Window)], Background, Foreground), - wxWindow:refresh(Window), + wxWindow:refresh(Window), Window; _ -> %% Get Parent who is a Panel setup_colors(wx:typeCast(wxWindow:getParent(Window), wxPanel), Background, Foreground) diff -Nru wings3d-2.2.4/src/wings_outliner.erl wings3d-2.2.5/src/wings_outliner.erl --- wings3d-2.2.4/src/wings_outliner.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_outliner.erl 2019-12-07 15:15:57.000000000 +0000 @@ -122,7 +122,7 @@ command({from_channel, {Ch, Id}}, _) -> from_channel(Ch, Id); command(Cmd, _) -> - io:format(?__(1,"NYI: ~p\n"), [Cmd]), + ?dbg(?__(1,"NYI: ~p\n"), [Cmd]), keep. prop_get_delete(Key, List) -> @@ -441,6 +441,10 @@ Pos = wxWindow:clientToScreen(TC, Pos0), Cmd = fun(_) -> wings_menu:popup_menu(TC, Pos, ?MODULE, Menu) end, wings_wm:psend(?MODULE, {apply, false, Cmd}); + undefined when Indx =:= 0 -> + Pos = wxWindow:clientToScreen(TC, Pos0), + wings ! {wm, {drop, Pos, Drag}}, + ignore; _ -> ignore end, @@ -458,7 +462,7 @@ #{type:=light, id:=Id} -> wings_wm:psend(?MODULE, {action, {?MODULE, {edit_light, Id}}}); What -> - io:format("~p:~p Item activated ~p~n", [?MODULE,?LINE, What]) + ?dbg("Item activated ~p~n", [What]) end, {noreply, State}; @@ -542,7 +546,7 @@ make_tree(Parent, #{bg:=BG, text:=FG}, IL) -> TreeStyle = ?wxTR_EDIT_LABELS bor ?wxTR_HIDE_ROOT bor ?wxTR_HAS_BUTTONS - bor ?wxTR_LINES_AT_ROOT bor ?wxTR_NO_LINES, + bor ?wxTR_LINES_AT_ROOT bor ?wxTR_NO_LINES bor wings_frame:get_border(), TC = set_pid(wxTreeCtrl:new(Parent, [{style, TreeStyle}]), self()), wxTreeCtrl:setBackgroundColour(TC, BG), wxTreeCtrl:setForegroundColour(TC, FG), diff -Nru wings3d-2.2.4/src/wings_palette.erl wings3d-2.2.5/src/wings_palette.erl --- wings3d-2.2.4/src/wings_palette.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_palette.erl 2019-12-07 15:15:57.000000000 +0000 @@ -260,7 +260,9 @@ wxWindow:destroy(Tmp), {ColsW,ColsH} = calc_size(Cols0,W,BW,false), %% io:format("Init Size ~p => ~p~n",[BSz, {ColsW, ColsH}]), - Win = wxScrolledWindow:new(Frame), + Win = wxScrolledWindow:new(Frame, [{style, wings_frame:get_border()}]), + #{bg:=BG} = wings_frame:get_colors(), + wxPanel:setBackgroundColour(Win, BG), Sz = wxGridSizer:new(ColsW, [{vgap, ?BORD},{hgap, ?BORD}]), Cols = add_empty(Cols0,ColsW,ColsH), manage_bitmaps(Win, Sz, Cols, [], Empty), @@ -523,9 +525,13 @@ none -> {false, Empty}; _ -> {true, make_bitmap(FloatCol)} end, - Static = case os:type() of - {win32, _} -> wxBitmapButton:new(Parent, Id, BM, [{style, ?wxBORDER_NONE}]); - {_, _} -> wxStaticBitmap:new(Parent, Id, BM) + Static = case {os:type(), {?wxMAJOR_VERSION, ?wxMINOR_VERSION}} of + {{win32, _},_} -> + wxBitmapButton:new(Parent, Id, BM, [{style, ?wxBORDER_NONE}]); + {{_,darwin},Ver} when Ver > {3,0} -> + wxBitmapButton:new(Parent, Id, BM, [{style, ?wxBORDER_NONE}]); + {_, _} -> + wxStaticBitmap:new(Parent, Id, BM) end, Delete andalso wxBitmap:destroy(BM), [wxWindow:connect(Static, Ev, [{skip, false}]) || @@ -554,9 +560,13 @@ setBitmap(Id, Bitmap, Parent) -> Win = wxWindow:findWindow(Parent, Id), wx:is_null(Win) andalso error({no_such_id, Id}), - case os:type() of - {win32, _} -> wxBitmapButton:setBitmapLabel(wx:typeCast(Win, wxBitmapButton), Bitmap); - {_, _} -> wxStaticBitmap:setBitmap(wx:typeCast(Win, wxStaticBitmap), Bitmap) + case {os:type(), {?wxMAJOR_VERSION, ?wxMINOR_VERSION}} of + {{win32, _},_} -> + wxBitmapButton:setBitmapLabel(wx:typeCast(Win, wxBitmapButton), Bitmap); + {{_,darwin}, Ver} when Ver > {3,0} -> + wxBitmapButton:setBitmapLabel(wx:typeCast(Win, wxBitmapButton), Bitmap); + {_, _} -> + wxStaticBitmap:setBitmap(wx:typeCast(Win, wxStaticBitmap), Bitmap) end. make_bitmap(Col0) -> diff -Nru wings3d-2.2.4/src/wings_pb.erl wings3d-2.2.5/src/wings_pb.erl --- wings3d-2.2.4/src/wings_pb.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_pb.erl 2019-12-07 15:15:57.000000000 +0000 @@ -107,10 +107,13 @@ S = #state{msg=["",Msg], t0=os:timestamp(), refresh=?REFRESH_T, level=1, pb=PB, frame=Frame, overlay=OV}, - wxFrame:show(OV), - wxGauge:show(PB), - wxFrame:raise(OV), - draw_position(S), + case wxFrame:isIconized(Frame) of + true -> ok; + false -> + wxFrame:show(OV), + wxGauge:show(PB), + draw_position(S) + end, {noreply, S, ?REFRESH_T}; handle_cast({start, Msg, percent}, #state{level=Level,next_pos=Next, pos=Pos,msg=Msg0, diff -Nru wings3d-2.2.4/src/wings_pick.erl wings3d-2.2.5/src/wings_pick.erl --- wings3d-2.2.4/src/wings_pick.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_pick.erl 2019-12-07 15:15:57.000000000 +0000 @@ -668,9 +668,9 @@ case wings_wm:lookup_prop(select_backface) of {value,true} -> %% Only in AutoUV windows. - wpc_pick:cull(false); + wings_pick_nif:cull(false); _ -> - wpc_pick:cull(true) + wings_pick_nif:cull(true) end, case dlo_pick(St, true, Ms) of [] -> none; @@ -678,7 +678,7 @@ end. set_pick_matrix(X, Y, Xs, Ys, W, H) -> - Pick = wpc_pick:pick_matrix(X, Y, Xs, Ys, {0,0,W,H}), + Pick = wings_pick_nif:pick_matrix(X, Y, Xs, Ys, {0,0,W,H}), ProjMatrix = wings_view:projection(Pick), {_, ModelMatrix} = wings_view:modelview(), set_pick_matrix_1(e3d_transform:matrix(ProjMatrix), @@ -691,7 +691,7 @@ set_pick_matrix_1(PM, e3d_mat:mul(MM, PostModel)). set_pick_matrix_1(ProjMatrix, ModelMatrix) -> - wpc_pick:matrix(ModelMatrix, ProjMatrix), + wings_pick_nif:matrix(ModelMatrix, ProjMatrix), {ProjMatrix, ModelMatrix}. %% update_selection({Mode,MM,{Id,Item}}, St0) -> @@ -876,10 +876,10 @@ Ms = set_pick_matrix(X, Y, W, H, Ww, Wh), case DrawFaces of true -> - wpc_pick:cull(true), + wings_pick_nif:cull(true), {dlo_pick(St, false, Ms),St}; false -> - wpc_pick:cull(false), + wings_pick_nif:cull(false), {marquee_pick(St, Ms),St} end. @@ -888,7 +888,7 @@ Vis = gb_sets:from_ordset(wings_we:visible(We)), EsPos = visible_edges(array:sparse_to_orddict(Etab), Vtab, Vis, []), - case wpc_pick:edges(EsPos) of + case wings_pick_nif:edges(EsPos) of [] -> Acc; Picked -> [{Id,E} || E <- Picked] ++ Acc @@ -904,7 +904,7 @@ Vs = wings_we:visible_vs(We), [{V,array:get(V, Vtab)} || V <- Vs] end, - case wpc_pick:vertices(VsPos) of + case wings_pick_nif:vertices(VsPos) of [] -> Acc; Picked -> @@ -938,9 +938,9 @@ setup_pick_context_fun(#dlo{mirror=Mirror,src_we=We}, PickFun, Ms, Acc0) -> Acc1 = PickFun(We, Acc0), set_pick_matrix(Ms, Mirror), - wpc_pick:front_face(cw), + wings_pick_nif:front_face(cw), Acc = PickFun(We, Acc1), - wpc_pick:front_face(ccw), + wings_pick_nif:front_face(ccw), set_pick_matrix(Ms), Acc. @@ -970,36 +970,36 @@ do_dlo_pick(wings_draw_setup:work(D, St), St, OneHit, Ms, Acc); do_dlo_pick(#dlo{mirror=none,src_we=#we{id=Id}=We}=D, _, OneHit, _Ms, Acc) when ?IS_AREA_LIGHT(We) -> - Cull = wpc_pick:culling(), - wpc_pick:cull(false), + Cull = wings_pick_nif:culling(), + wings_pick_nif:cull(false), Res = do_dlo_pick_0(Id, D, OneHit, Acc), - wpc_pick:cull(Cull), + wings_pick_nif:cull(Cull), Res; do_dlo_pick(#dlo{mirror=none,open=Open,src_we=#we{id=Id}}=D, _, OneHit, _Ms, Acc) -> case wings_pref:get_value(show_backfaces) of - true when Open -> wpc_pick:front_face(cw); - _ -> wpc_pick:front_face(ccw) + true when Open -> wings_pick_nif:front_face(cw); + _ -> wings_pick_nif:front_face(ccw) end, do_dlo_pick_0(Id, D, OneHit, Acc); do_dlo_pick(#dlo{mirror=Matrix,open=Open,src_we=#we{id=Id}}=D0, _, OneHit, Ms, Acc0) -> case wings_pref:get_value(show_backfaces) of true when Open -> - wpc_pick:front_face(cw), + wings_pick_nif:front_face(cw), {D1,Acc1} = do_dlo_pick_0(Id, D0, OneHit, Acc0); _ -> {D1,Acc1} = do_dlo_pick_0(Id, D0, OneHit, Acc0), - wpc_pick:front_face(cw) + wings_pick_nif:front_face(cw) end, set_pick_matrix(Ms, Matrix), {D,Acc} = do_dlo_pick_0(-Id, D1, OneHit, Acc1), - wpc_pick:front_face(ccw), + wings_pick_nif:front_face(ccw), set_pick_matrix(Ms), {D,Acc}. do_dlo_pick_0(Id, #dlo{vab=#vab{data=VsBin,face_vs={Stride,_},face_map=Map0}}=D0, OneHit, Acc0) -> Vs = {Stride,VsBin}, - case wpc_pick:faces(Vs, OneHit) of + case wings_pick_nif:faces(Vs,OneHit) of [] -> %% No hit. {D0,Acc0}; diff -Nru wings3d-2.2.4/src/wings_pick_nif.erl wings3d-2.2.5/src/wings_pick_nif.erl --- wings3d-2.2.4/src/wings_pick_nif.erl 1970-01-01 00:00:00.000000000 +0000 +++ wings3d-2.2.5/src/wings_pick_nif.erl 2019-12-07 15:15:57.000000000 +0000 @@ -0,0 +1,407 @@ +%% +%% wings_pick_nif.erl -- +%% +%% This module handles picking. +%% +%% Copyright (c) 2009-2019 Bjorn Gustavsson & Dan Gudmundsson +%% +%% See the file "license.terms" for information on usage and redistribution +%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% This module and driver implements picking of faces, edges, and vertices +%% without using the selection support in OpenGL. The selection support in +%% OpenGL is not use by many applications, so it may not work reliably in +%% all implementations of OpenGL. +%% +%% Conceptually, we use the same algorithm as OpenGL. We set up a special +%% view volume that only includes a few pixels on each side of the mouse +%% cursor (or what is inside the marquee) using a special pick matrix. +%% We then go through all objects in the scene to which faces/edges/vertices +%% fall inside the view volume. +%% +%% We use a different pick matrix and view volume than OpenGL, namely +%% 0 <= X <= 1, 0 <= Y <= 1, 0 <= Z <= 1 instead of -1 <= X <= 1, +%% -1 <= Y <= 1, -1 <= Z <= 1 as suggested by Jim Blinn in +%% A Trip Down the Graphics Pipeline. +%% +%% Main references: +%% +%% Jim Blinn: A Trip Down the Graphics Pipeline. Chapter 13: Line Clipping. +%% +%% Paul Heckbert: Generic Convex Polygon Scan Conversion and Clipping in +%% Graphics Gems. +%% +-module(wings_pick_nif). +-export([pick_matrix/5,matrix/2,cull/1,culling/0,front_face/1, + faces/2,vertices/1,edges/1]). + +-on_load(init/0). + +-import(lists, [foldl/3,last/1,sort/1]). +-define(FL, 32/native-float). + +%% Comment out the following line to use the pure Erlang +%% reference implementation. +-define(USE_DRIVER, 1). +-define(nif_stub,nif_stub_error(?LINE)). +nif_stub_error(Line) -> + erlang:nif_error({nif_not_loaded,module,?MODULE,line,Line}). + +init() -> + case get(wings_not_running) of + undefined -> + Name = "wings_pick_nif", + Dir = case code:priv_dir(wings) of + {error, _} -> filename:join(wings_util:lib_dir(wings),"priv"); + Priv -> Priv + end, + Nif = filename:join(Dir, Name), + erlang:load_nif(Nif, 0); + _ -> + false + end. + +%% pick_matrix(X, Y, Xs, Ys, ViewPort) -> PickMatrix +%% Set up a pick matrix like glu:pickMatrix/5, +%% but with a diffrent viewing volume (the cube (0...1)^3 +%% instead of (-1...1)^3). +%% +pick_matrix(X, Y, Xs, Ys, ViewPort) -> + M0 = e3d_transform:translate(e3d_transform:identity(), {0.5, 0.5, 0.5}), + M1 = e3d_transform:scale(M0, {0.5,0.5,0.5}), + Pick = e3d_transform:pick(X, Y, Xs, Ys, ViewPort), + e3d_transform:mul(M1, Pick). + +%% matrix(ModelViewMatrix, ProjectionMatrix) +%% Set the matrix to use for picking by combining the model +%% view and projection matrices. +%% +matrix(Model, Proj) when is_list(Model) -> + matrix(list_to_tuple(Model), Proj); +matrix(Model, Proj) when is_list(Proj) -> + matrix(Model, list_to_tuple(Proj)); +matrix(Model, Proj) -> + Mat = e3d_mat:mul(Proj, Model), + put({?MODULE,matrix}, Mat), + ok. + +%% cull(true|false) +%% Enable or disable backface culling when picking. +cull(false) -> + erase({?MODULE,cull}), + ok; +cull(true) -> + put({?MODULE,cull}, true), + ok. + +culling() -> + get({?MODULE,cull}) =:= true. + +%% front_face(ccw|cw) +%% Define the vertex order for front facing triangles. +front_face(ccw) -> + erase({?MODULE,front_face}), + ok; +front_face(cw) -> + put({?MODULE,front_face}, cw), + ok. + +%% faces({Stride,VertexBuffer}, OneHit) -> {Index,Depth} | [Index] +%% Depth = 0..2^32-1 (0 means the near clipping plane) +%% Given a vertex buffer containing triangles, either return +%% {Index,Depth} (if OneHit is 'true'), or a list of indices for each +%% triangle that are wholly or partly inside the viewing volume +%% (if OneHit is 'false'). +%% +-ifdef(USE_DRIVER). +faces({_,<<>>}, _) -> + %% An empty binary is most probably not reference-counted, + %% so we must *not* send it down to the driver. (The length + %% of the I/O vector will be 2, not 3, and the driver will + %% ignore the request without sending any data back to us.) + []; +faces({Stride, Bin}, OneHit) -> + faces_1(Stride, Bin, OneHit, + get({?MODULE,cull}) =:= true, + get({?MODULE,front_face}) /= cw, + get({?MODULE,matrix}) + ). + +faces_1(_, _, _, _, _, _) -> + ?nif_stub. + +-else. +faces({Stride,Bin}, OneHit) -> + Matrix = get({?MODULE,matrix}), + Cull0 = get({?MODULE,cull}) =:= true, + CwIsFront = get({?MODULE,front_face}) =:= cw, + Cull = case {Cull0,CwIsFront} of + {false,_} -> none; + {true,true} -> cull_ccw; + {true,false} -> cull_cw + end, + faces_1(Bin, Stride-12, Matrix, Cull, OneHit, 0, []). +-endif. + +%% vertices([{Vertex,Position}]) -> [Vertex] +%% Return a list of all vertices that are inside the +%% viewing volume. +%% +vertices(VsPos) -> + Matrix = get({?MODULE,matrix}), + vertices_1(VsPos, Matrix, []). + +%% vertices([{Edge,Position}]) -> [Edges] +%% Return a list of all edges that are inside the +%% viewing volume. +%% +edges(EsPos) -> + Matrix = get({?MODULE,matrix}), + edges_1(EsPos, Matrix, []). + +%%% +%%% Internal functions. +%%% + +vertices_1([{V,{X0,Y0,Z0}}|T], Mat, Acc) -> + {X,Y,Z,W} = e3d_mat:mul(Mat, {X0,Y0,Z0,1.0}), + Inside = X >= 0 andalso W-X >= 0 andalso + Y >= 0 andalso W-Y >= 0 andalso + Z >= 0 andalso W-Z >= 0, + case Inside of + true -> + vertices_1(T, Mat, [V|Acc]); + false -> + vertices_1(T, Mat, Acc) + end; +vertices_1([], _, Acc) -> Acc. + +edges_1([{E,{P0,P1}}|T], Mat, Acc) -> + case edge_visible(P0, P1, Mat) of + false -> edges_1(T, Mat, Acc); + true -> edges_1(T, Mat, [E|Acc]) + end; +edges_1([], _, Acc) -> + Acc. + +edge_visible({X0,Y0,Z0}, {X1,Y1,Z1}, Mat) -> + Pos0 = e3d_mat:mul(Mat, {X0,Y0,Z0,1.0}), + Pos1 = e3d_mat:mul(Mat, {X1,Y1,Z1,1.0}), + OutCode0 = outcode(Pos0), + OutCode1 = outcode(Pos1), + case OutCode0 band OutCode1 of + 0 -> + case OutCode0 bor OutCode1 of + 0 -> + %% Both endpoints are inside all planes. Trivial accept. + true; + Clip -> + non_trivial_edge_visible(OutCode0, Clip, Pos0, Pos1, + 32, 0.0, 1.0) + end; + _ -> + %% Both endpoints are outside the same plane. Trivial reject. + false + end. + +non_trivial_edge_visible(_Code0, _Clip, _P0, _P1, 0, _A0, _B0) -> + %% The line has been clipped against all planes and + %% rejected. Non-trivial accept. + true; +non_trivial_edge_visible(Code0, Clip, P0, P1, Plane, A0, B0) -> + case Clip band Plane of + 0 -> + %% Both endpoints are on the inside side of the plane. + %% Continue with the next plane. + non_trivial_edge_visible(Code0, Clip, P0, P1, Plane bsr 1, A0, B0); + _ -> + %% One endpoint outside, one inside. + Dot0 = pdot2(Plane, P0), + Dot1 = pdot2(Plane, P1), + NewAlpha = Dot0 / (Dot0 - Dot1), + {A,B} = case Code0 band Plane of + 0 -> + {A0,min(B0, NewAlpha)}; + _ -> + {max(A0, NewAlpha),B0} + end, + if + B < A -> + %% The line is completely outside of all planes. + %% Non-trivial reject. + false; + true -> + non_trivial_edge_visible(Code0, Clip, P0, P1, Plane bsr 1, A, B) + end + end. + +-define(SHIFT_OUT(Code, Dot), if Dot < 0.0 -> (Code bsl 1) bor 1; + true -> Code bsl 1 end). +outcode({X,_,_,_}=P) -> + outcode_1(P, ?SHIFT_OUT(0, X)). + +outcode_1({X,_,_,W}=P, Code) -> + outcode_2(P, ?SHIFT_OUT(Code, W-X)). + +outcode_2({_,Y,_,_}=P, Code) -> + outcode_3(P, ?SHIFT_OUT(Code, Y)). + +outcode_3({_,Y,_,W}=P, Code) -> + outcode_4(P, ?SHIFT_OUT(Code, W-Y)). + +outcode_4({_,_,Z,_}=P, Code) -> + outcode_5(P, ?SHIFT_OUT(Code, Z)). + +outcode_5({_,_,Z,W}, Code) -> + ?SHIFT_OUT(Code, W-Z). + +pdot2(32, {X,_,_,_}) -> X; +pdot2(16, {X,_,_,W}) -> W-X; +pdot2(8, {_,Y,_,_}) -> Y; +pdot2(4, {_,Y,_,W}) -> W-Y; +pdot2(2, {_,_,Z,_}) -> Z; +pdot2(1, {_,_,Z,W}) -> W-Z. + + +%%% +%%% Reference implementation in pure Erlang of face picking. +%%% + +-ifndef(USE_DRIVER). +faces_1(Bin0, Unused, Mat, Cull, OneHit, I, Acc) -> + case Bin0 of + <> -> + Tri0 = [{X1,Y1,Z1,1.0},{X2,Y2,Z2,1.0},{X3,Y3,Z3,1.0}], + Tri = [e3d_mat:mul(Mat, P) || P <- Tri0], + case clip_tri(Tri, Cull) of + [] -> + %% Outside. + faces_1(Bin, Unused, Mat, Cull, OneHit, I+3, Acc); + [{_,_,Z,W}|_] -> + %% Inside. Now clipped to the view frustum. + Depth = round((Z/W)*16#FFFFFFFF), + faces_1(Bin, Unused, Mat, Cull, OneHit, I+3, [{Depth,I}|Acc]) + end; + <<>> -> + Hits = sort(Acc), + case OneHit of + false -> + sort([Hit || {_,Hit} <- Hits]); + true -> + case Hits of + [] -> []; + [{Depth,Hit}|_] -> {Hit,Depth} + end + end + end. + +clip_tri(Tri, Cull) -> + case clip_tri_1(Tri) of + [] -> []; + Vs -> + Inside = case Cull of + none -> true; + cull_cw -> is_ccw(Vs); + cull_ccw -> not is_ccw(Vs) + end, + case Inside of + true -> Vs; + false -> [] + end + end. + +clip_tri_1(Tri) -> + OutCodes = [outcode(P) || P <- Tri], + And = foldl(fun(Code, Acc) -> Code band Acc end, 16#3F, OutCodes), + case And of + 0 -> + Or = foldl(fun(Code, Acc) -> Code bor Acc end, 0, OutCodes), + case Or of + 0 -> + %% All vertices are inside all planes. Trivial accept. + Tri; + _ -> + %% Handle the non-trivial cases. + non_trivial(Tri) + end; + _ -> + %% All vertices are outside at least one of the planes. + %% Trivial reject. + [] + end. + +non_trivial(Ps) -> + non_trivial_1(Ps, 6). + +non_trivial_1(Ps, 0) -> + %% Checked against all planes and not rejected. Non-trivial accept. + Ps; +non_trivial_1(Ps0, Plane) -> + case non_trivial_2(last(Ps0), Ps0, Plane) of + Ps when length(Ps) < 3 -> + %% No longer a triangle or polygon. Non-trivial reject. + []; + Ps -> + non_trivial_1(Ps, Plane-1) + end. + +non_trivial_2(Prev, [P|T], Plane) -> + case {out(Plane, Prev),out(Plane, P)} of + {false,false} -> + %% Both inside. Keep current vertex (P). + [P|non_trivial_2(P, T, Plane)]; + {false,true} -> + %% Previous inside, current outside. + %% Keep intersection of edge and clipping plane. + [intersection(Prev, P, Plane)|non_trivial_2(P, T, Plane)]; + {true,false} -> + %% Previous outside, current inside. + %% Keep intersection of edge and clipping plane, + %% followed by current. + [intersection(Prev, P, Plane),P|non_trivial_2(P, T, Plane)]; + {true,true} -> + %% Both outside. Remove the current vertex. + non_trivial_2(P, T, Plane) + end; +non_trivial_2(_, [], _) -> []. + +is_ccw([{X1,Y1,_,W1},{X2,Y2,_,W2},{X3,Y3,_,W3}|_]) -> + XWinC = X3/W3, + YWinC = Y3/W3, + DxAC = X1/W1 - XWinC, + DxBC = X2/W2 - XWinC, + DyAC = Y1/W1 - YWinC, + DyBC = Y2/W2 - YWinC, + Area = DxAC * DyBC - DxBC * DyAC, + Area >= 0.0. + +intersection(P0, P1, Plane) -> + Dot0 = pdot(Plane, P0), + Dot1 = pdot(Plane, P1), + A = Dot0 / (Dot0 - Dot1), + add(P0, mul(sub(P1, P0), A)). + +mul({V10,V11,V12,V13}, S) when is_float(S) -> + {V10*S,V11*S,V12*S,V13}. + +sub({V10,V11,V12,V13}, {V20,V21,V22,V23}) -> + {V10-V20,V11-V21,V12-V22,V13-V23}. + +add({V10,V11,V12,V13}, {V20,V21,V22,V23}) + when is_float(V10), is_float(V11), is_float(V12) -> + {V10+V20,V11+V21,V12+V22,V13+V23}. + +out(Plane, P) -> pdot(Plane, P) < 0.0. + +pdot(1, {X,_,_,_}) -> X; +pdot(2, {X,_,_,W}) -> W-X; +pdot(3, {_,Y,_,_}) -> Y; +pdot(4, {_,Y,_,W}) -> W-Y; +pdot(5, {_,_,Z,_}) -> Z; +pdot(6, {_,_,Z,W}) -> W-Z. +-endif. diff -Nru wings3d-2.2.4/src/wings_pref_dlg.erl wings3d-2.2.5/src/wings_pref_dlg.erl --- wings3d-2.2.4/src/wings_pref_dlg.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_pref_dlg.erl 2019-12-07 15:15:57.000000000 +0000 @@ -503,7 +503,10 @@ workaround([ {jumpy_camera, ?__(19,"Camera moves and interactive commands are jumpy"), - ?__(20,"Problem occurs on Mac OS X 10.3 (Panther)")}, + ?__(20,"Decreases large jumps in mouse coordinate changes")}, + {no_warp, + ?__(31,"Camera moves and interactive commands not working"), + ?__(32,"Minimize mouse moves programatically")}, {ungrab_bug, ?__(26,"Camera moves steals focus"), ?__(27,"Problem occurs on linux")} @@ -522,7 +525,6 @@ [{?__(28,"Maximum menu height in pixels"),max_menu_height, [{info,?__(29,"Menus are clipped and continue in 'More...' submenu.") ++" "++?__(30,"Less than 1 sets menu clipping to auto.")}]}]}]}]} - ]}. workaround(L) -> diff -Nru wings3d-2.2.4/src/wings_pref.erl wings3d-2.2.5/src/wings_pref.erl --- wings3d-2.2.4/src/wings_pref.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_pref.erl 2019-12-07 15:15:57.000000000 +0000 @@ -330,7 +330,6 @@ wings_theme:native_theme()++ [ %% Put any non-constant preferences here. - {jumpy_camera,os:type() =:= {unix,darwin}}, %% Advanced menus are always turned on now. %% The default must still be false for compatibility @@ -371,6 +370,8 @@ {polygon_offset_r,1.0}, {multisample, true}, {ungrab_bug, false}, + {no_warp, false}, + {jumpy_camera, false}, %% Advanced features. {default_commands,false}, diff -Nru wings3d-2.2.4/src/wings_render.erl wings3d-2.2.5/src/wings_render.erl --- wings3d-2.2.4/src/wings_render.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_render.erl 2019-12-07 15:15:57.000000000 +0000 @@ -656,10 +656,10 @@ wings_dl:call(Ns, RS). edge_width(edge) -> wings_pref:get_value(edge_width); -edge_width(_) -> 1. +edge_width(_) -> 1.0. hard_edge_width(edge) -> wings_pref:get_value(hard_edge_width); -hard_edge_width(_) -> max(wings_pref:get_value(hard_edge_width) - 1, 1). +hard_edge_width(_) -> max(wings_pref:get_value(hard_edge_width) - 1, 1.0). ground_and_axes(#view{yon=Yon0} = View, PM,MM) -> Axes = wings_wm:get_prop(show_axes), @@ -708,7 +708,7 @@ gl:matrixMode(?GL_PROJECTION), gl:loadIdentity(), {_,_,W,H} = ViewPort, - glu:ortho2D(0.0, W, H, 0.0), + glu:ortho2D(0.0, float(W), float(H), 0.0), gl:matrixMode(?GL_MODELVIEW), gl:loadIdentity(), gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL), @@ -822,8 +822,9 @@ clip_1(_, [], _W) -> none. show_letter(X0, Y0, W, Char, {_,_,Vw,Vh}) -> - X = trunc((0.5*X0/W+0.5)*(Vw-20) + 10), - Y = Vh - trunc((0.5*Y0/W+0.5)*(Vh-16) - 1), + PosX = trunc((0.5*X0/W+0.5)*(Vw-20) + 10), + X = max(5, min(Vw-20, PosX)), + Y = max(30, min(Vh-20, Vh - trunc((0.5*Y0/W+0.5)*(Vh-16) - 1))), axis_text(X, Y, Char). axis_text(X, Y, C) -> diff -Nru wings3d-2.2.4/src/wings_shaders.erl wings3d-2.2.5/src/wings_shaders.erl --- wings3d-2.2.4/src/wings_shaders.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_shaders.erl 2019-12-07 15:15:57.000000000 +0000 @@ -124,7 +124,7 @@ clear_state(Id, Rs0) -> case maps:get(shader, Rs0) of - 0 -> {false, Rs0}; + 0 -> Rs0; #{name:=Name} -> maps:remove({Name, Id}, Rs0) end. diff -Nru wings3d-2.2.4/src/wings_status.erl wings3d-2.2.5/src/wings_status.erl --- wings3d-2.2.4/src/wings_status.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_status.erl 2019-12-07 15:15:57.000000000 +0000 @@ -33,16 +33,16 @@ {ok, wx_object:get_pid(Status)}. message(Win, Str) -> - wx_object:cast(?MODULE, {message, Win, Str, undefined}). + catch wx_object:cast(?MODULE, {message, Win, Str, undefined}). message(Win, Left, Right) -> - wx_object:cast(?MODULE, {message, Win, Left, Right}). + catch wx_object:cast(?MODULE, {message, Win, Left, Right}). message_right(Win, Right) -> - wx_object:cast(?MODULE, {message, Win, undefined, Right}). + catch wx_object:cast(?MODULE, {message, Win, undefined, Right}). active(Win) -> - wx_object:cast(?MODULE, {active, Win}). + catch wx_object:cast(?MODULE, {active, Win}). get_statusbar() -> wx_object:call(?MODULE, get_statusbar). diff -Nru wings3d-2.2.4/src/wings_text.erl wings3d-2.2.5/src/wings_text.erl --- wings3d-2.2.4/src/wings_text.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_text.erl 2019-12-07 15:15:57.000000000 +0000 @@ -157,7 +157,7 @@ {glyph_info(Font, width), glyph_info(Font, height)}. render(X, Y, S) -> - Res = render_1(S, Font=current_font(), {X, Y, <<>>}), + Res = render_1(S, Font=current_font(), {X, Y, X, <<>>}), gl:pushAttrib(?GL_TEXTURE_BIT bor ?GL_ENABLE_BIT), gl:enable(?GL_BLEND), gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA), @@ -167,9 +167,9 @@ gl:popAttrib(), ok. -render_1([{bold, S}|Rest], Font, {X0, Y0, _} = Acc0) -> - {_, _, Bin} = wings_glfont:render_to_binary(Font, S, Acc0), - Acc = wings_glfont:render_to_binary(Font, S, {X0+1, Y0, Bin}), +render_1([{bold, S}|Rest], Font, {X0, Y0, XS, _} = Acc0) -> + {_, _, _, Bin} = wings_glfont:render_to_binary(Font, S, Acc0), + Acc = wings_glfont:render_to_binary(Font, S, {X0+1, Y0, XS, Bin}), render_1(Rest, Font, Acc); render_1([{ul,S}|Cs], Font, Acc0) -> %% ignore for now Acc = wings_glfont:render_to_binary(Font, S, Acc0), @@ -181,7 +181,7 @@ render_1(Cs, Font, char(C, Font, Acc)); render_1([], _, Acc) -> Acc. -char(C, Font, Acc) when is_integer(C) -> +char(C, Font, Acc) when is_integer(C) -> wings_glfont:render_to_binary(Font, [C], Acc); char(C, Font, Acc) when is_atom(C) -> case special(C) of diff -Nru wings3d-2.2.4/src/wings_theme.erl wings3d-2.2.5/src/wings_theme.erl --- wings3d-2.2.4/src/wings_theme.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_theme.erl 2019-12-07 15:15:57.000000000 +0000 @@ -73,7 +73,10 @@ {menu_hilite, menuhilight}, {menu_hilited_text,highlighttext}, {menu_text, menutext}, - {outliner_geograph_bg, menu}, + %% Need to differ from c3dface if no borders is drawn + %% Since the splitter seems to be drawn with c3dface color + %% and can't be set to another color (in erlang) + {outliner_geograph_bg, c3dshadow}, {outliner_geograph_disabled, graytext}, {outliner_geograph_hl, highlight}, {outliner_geograph_hl_text, highlighttext}, @@ -82,7 +85,8 @@ {info_line_text,windowtext}, {title_active_color, activecaption}, {title_passive_color,inactivecaption}, - {title_text_color, captiontext} + {title_text_color, captiontext}, + {title_passive_text_color, inactivecaptiontext} ], OS = element(2, os:type()), Version = os:version(), @@ -109,20 +113,38 @@ {_, _, V} when V > 0.5 -> {0.0,0.0,0.0}; _ -> {1.0,1.0,1.0} end}; -native({What=menu_hilite, menuhilight}, darwin, Ver) -> - native({What, highlight}, darwin, Ver); -native({What, activecaption}, darwin, Ver) -> - native({What, btnhighlight}, darwin, Ver); -native({What=outliner_geograph_bg, Suggestion}, _, _) -> - {What, wings_color:rgb4fv(wxSystemSettings:getColour(wx_id(Suggestion)))}; -native({What=menu_color, Suggestion}, nt, _) -> - {What, wings_color:rgb4fv(wxSystemSettings:getColour(wx_id(Suggestion)))}; +native({What=menu_color, menu}, darwin, Ver) -> + native({What, c3dface}, darwin, Ver); +native({What=outliner_geograph_bg, c3dshadow}, darwin, Ver) -> + native({What, c3dface}, darwin, Ver); +native({What=title_active_color, activecaption}, darwin, _Ver) -> + {What, e3d_vec:mul(default(c3dface), 1.07)}; +native({What=title_passive_color, inactivecaption}, darwin, _Ver) -> + {What, e3d_vec:mul(default(c3dface), 0.92)}; +native({What=title_active_color, activecaption}, linux, _Ver) -> + {What, e3d_vec:mul(default(c3dface), 1.07)}; +native({What=title_passive_color, inactivecaption}, linux, _Ver) -> + {What, e3d_vec:mul(default(inactivecaption), 0.92)}; native({What, Suggestion}, _, _) -> + %% ?dbg("Color ~p ~p ~p => ~p~n", + %% [What, Suggestion, wxSystemSettings:getColour(wx_id(Suggestion)), + %% default(Suggestion)]), {What, default(Suggestion)}. default(Suggestion) -> - wings_color:rgb3fv(wxSystemSettings:getColour(wx_id(Suggestion))). + rgb3fv(wxSystemSettings:getColour(wx_id(Suggestion))). +rgb3fv({R,G,B,A0}) -> + %% Mac delivers some colors as alpha only + case (A0 < 250) andalso (R+G+B < 180) of + true -> + %% Hack to lighten black colors with alpha + A = (255-A0)/255, + {A*(200-R)/255,A*(200-G)/255,A*(200-B)/255}; + false -> + A = A0/255, + {A*R/255,A*G/255,A*B/255} + end. %%% %%% Legacy Colors - Classic Green Theme %%% @@ -162,9 +184,10 @@ {title_active_color,{0.41,0.55,0.41,1.0}}, {title_passive_color,{0.325,0.4,0.325,1.0}}, {title_text_color,{1.0,1.0,1.0}}, + {title_passive_text_color,{0.7,0.7,0.7}}, {menu_bar_bg,{0.52,0.52,0.52}}, {menubar_text,{0.0,0.0,0.0}}, - {info_line_bg,{0.52,0.52,0.52}}, + {info_line_bg,{0.72,0.72,0.72}}, {info_line_text,{1.0,1.0,1.0}}, %% Console @@ -199,7 +222,7 @@ {hard_edge_color,{1.0,0.5,0.0}}, {info_background_color,{0.3,0.2999996923080079,0.2999996923080079,0.5}}, {info_color,{1.0,1.0,1.0}}, - {info_line_bg,{0.11200000000000002,0.3932173913043481,0.7}}, + {info_line_bg,{0.3,0.2999996923080079,0.2999996923080079,0.5}}, {info_line_text,{1.0,1.0,1.0}}, {masked_vertex_color,{0.5,1.0,0.0,0.8}}, {material_default,{0.8200000000000001,0.9049993849536925,1.0}}, @@ -229,6 +252,7 @@ {title_passive_color, {0.5270222222222222,0.5873069411419368,0.6533333333333333,0.9}}, {title_text_color,{1.0,1.0,1.0}}, + {title_passive_text_color,{0.7,0.7,0.7}}, {tweak_magnet_color,{0.0,0.0,1.0,0.06}}, {tweak_vector_color,{1.0,0.5,0.0}}, {unselected_hlite, @@ -287,6 +311,7 @@ {title_active_color,{0.647059,0.7137254901960784,0.3254901960784314,1.0}}, {title_passive_color,{0.4141418073872209,0.5,0.0,1.0}}, {title_text_color,{1.0,1.0,1.0}}, + {title_passive_text_color,{0.7,0.7,0.7}}, {tweak_magnet_color,{0.0,0.0,1.0,0.06}}, {tweak_vector_color,{1.0,0.5,0.0}}, {unselected_hlite,{0.0,0.65,0.0}}, @@ -346,6 +371,7 @@ {title_active_color,{0.07,0.4,0.7,0.9}}, {title_passive_color,{0.05,0.27,0.5,0.9}}, {title_text_color,{1.0,1.0,1.0}}, + {title_passive_text_color,{0.7,0.7,0.7}}, {tweak_magnet_color,{0.0,0.0,1.0,0.06}}, {tweak_vector_color,{1.0,0.5,0.0}}, {unselected_hlite,{0.0,0.65,0.0}}, diff -Nru wings3d-2.2.4/src/wings_tweak.erl wings3d-2.2.5/src/wings_tweak.erl --- wings3d-2.2.4/src/wings_tweak.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_tweak.erl 2019-12-07 15:15:57.000000000 +0000 @@ -45,8 +45,10 @@ mag_rad, % magnet influence radius id, % {Id,Elem} mouse was over when tweak began sym, % current magnet radius adjustment hotkey - ox,oy, % original X,Y - cx,cy, % current X,Y + ox,oy, % orignal X,Y + x,y, % current X,Y + cx,cy, % Calculated X,Y + warp, % true or size limits clk=none, % click selection/deselection st}). % wings st record (working) @@ -169,8 +171,12 @@ case orddict:find({B,{Ctrl,Shift,Alt}}, TweakKeys) of {ok, Mode} -> {Mag,MagType,MagR} = wings_pref:get_value(tweak_magnet), - T = #tweak{mode=Mode,ox=X,oy=Y,magnet=Mag,mag_type=MagType, - mag_rad=MagR,st=St}, + Warp = case wings_pref:get_value(no_warp, false) of + false -> true; + true -> wings_wm:win_size() + end, + T = #tweak{mode=Mode,warp=Warp,ox=X,oy=Y,x=X,y=Y, + magnet=Mag,mag_type=MagType,mag_rad=MagR,st=St}, handle_tweak_event_1(T); error -> next end; @@ -245,7 +251,7 @@ %%% Start Tweak %%% -handle_tweak_event_1(#tweak{ox=X,oy=Y, st=#st{sel=Sel}=St0}=T) -> +handle_tweak_event_1(#tweak{x=X,y=Y, st=#st{sel=Sel}=St0}=T) -> case wings_pick:do_pick(X,Y,St0) of {add, What, St} when Sel =:= [] -> from_element_point(X,Y,St0), @@ -281,7 +287,7 @@ wings:redraw(St), initiate_tweak_handler(What, St, T); handle_initial_event(#mousebutton{button=1,state=?SDL_RELEASED}, What, #st{shapes=Shs,sel=Sel0}=St0, - #tweak{id={Action,{Id,[Elem]}},clk=none,ox=X,oy=Y}=T) -> + #tweak{id={Action,{Id,[Elem]}},clk=none,x=X,y=Y}=T) -> case wings_io:is_grabbed() of false -> ok; true -> wings_io:ungrab(X,Y) @@ -313,7 +319,7 @@ wings_wm:dirty(), initiate_tweak_handler(What, St, T#tweak{clk={one,os:timestamp()}}); handle_initial_event(#mousebutton{button=1,x=X0,y=Y0,state=?SDL_PRESSED}=Ev, - _What, St, #tweak{clk={one,Clk},ox=X,oy=Y,st=TweakSt}) -> + _What, St, #tweak{clk={one,Clk},x=X,y=Y,st=TweakSt}) -> case timer:now_diff(os:timestamp(),Clk) < wings_pref:get_value(tweak_click_speed) of true -> wings_pick:paint_pick(X0, Y0, TweakSt); @@ -328,13 +334,13 @@ wings_wm:later({new_state,St}), pop; handle_initial_event(#mousemotion{x=X,y=Y}=Ev, What, St, - #tweak{ox=OX,oy=OY,cx=CX,cy=CY,clk=Clk}=T) -> + #tweak{x=OX,y=OY,cx=CX,cy=CY,clk=Clk}=T0) -> DX = X-OX, %since last move X DY = Y-OY, %since last move Y DxOrg = DX+CX, %total X DyOrg = DY+CY, %total Y Total = math:sqrt(DxOrg * DxOrg + DyOrg * DyOrg), - wings_io:warp(OX,OY), + T = mouse_warp(X,Y,T0), case Total > 3 of true when Clk =:= none -> enter_tweak_handler(Ev, What, St, T); @@ -348,7 +354,7 @@ wings_wm:send_after_redraw(geom,Ev), wings_wm:later({new_state,St}), pop; -handle_initial_event(#keyboard{sym=Sym,mod=Mod}=Ev, What, St, #tweak{ox=X,oy=Y}=T) +handle_initial_event(#keyboard{sym=Sym,mod=Mod}=Ev, What, St, #tweak{x=X,y=Y}=T) when Mod band (?ALT_BITS bor ?SHIFT_BITS bor ?CTRL_BITS) =:= 0 -> %% Activate Tweak Camera case wings_camera:tweak_camera_event(Sym, X, Y, St) of @@ -422,7 +428,7 @@ %% Tweak Modes that can be modified by xyz constraints handle_tweak_drag_event_0(Ev, T0#tweak{mode=TwkMode}); handle_tweak_drag_event_0(#mousemotion{x=X,y=Y}, - #tweak{mode=TweakMode,ox=OX,oy=OY,cx=CX,cy=CY}=T0) -> + #tweak{mode=TweakMode,x=OX,y=OY,cx=CX,cy=CY}=T0) -> Mode = case TweakMode of move -> actual_mode(TweakMode); @@ -435,9 +441,9 @@ DY = Y-OY, %since last move Y DxOrg = DX+CX, %total X DyOrg = DY+CY, %total Y - wings_io:warp(OX,OY), + T1 = mouse_warp(X,Y,T0), do_tweak_0(DX,DY,DxOrg,DyOrg,Mode), - T = T0#tweak{mode=Mode,cx=DxOrg,cy=DyOrg}, + T = T1#tweak{mode=Mode,cx=DxOrg,cy=DyOrg}, update_tweak_handler(T); %%% @@ -459,7 +465,7 @@ update_tweak_handler(T); false -> keep end; -handle_tweak_drag_event_0(#keyboard{sym=Sym,mod=Mod}=Ev, #tweak{ox=OX,oy=OY,st=St}=T) +handle_tweak_drag_event_0(#keyboard{sym=Sym,mod=Mod}=Ev, #tweak{x=OX,y=OY,st=St}=T) when Mod band (?ALT_BITS bor ?SHIFT_BITS bor ?CTRL_BITS) =:= 0 -> %% Activate Tweak Camera case wings_camera:tweak_camera_event(Sym, OX, OY, St) of @@ -541,7 +547,7 @@ wings_io:grab(), begin_magnet_adjustment(What, St), tweak_magnet_radius_help(true), - T = T0#tweak{id=IdElem,ox=X,oy=Y,cx=0,cy=0}, + T = T0#tweak{id=IdElem,ox=X,oy=Y,x=X,y=Y,cx=0,cy=0}, {seq,push,update_magnet_handler(T)}. %%% @@ -562,10 +568,10 @@ update_magnet_handler(T); handle_magnet_event({new_state,St}, T) -> end_magnet_event(T#tweak{st=St}); -handle_magnet_event(#mousemotion{x=X},#tweak{ox=OX, oy=OY}=T0) -> +handle_magnet_event(#mousemotion{x=X,y=Y},#tweak{x=OX}=T0) -> DX = X-OX, %since last move X - wings_io:warp(OX,OY), - T = adjust_magnet_radius(DX,T0), + T1 = mouse_warp(X,Y,T0), + T = adjust_magnet_radius(DX,T1), update_magnet_handler(T); %% If something is pressed during magnet radius adjustment, save changes %% and begin new event. @@ -589,15 +595,15 @@ tweak_drag_mag_adjust(#tweak{st=#st{selmode=body}}) -> keep; tweak_drag_mag_adjust(#tweak{magnet=false}) -> keep; -tweak_drag_mag_adjust(#tweak{mode=Mode, cx=CX, cy=CY, ox=OX, oy=OY}=T0) -> +tweak_drag_mag_adjust(#tweak{mode=Mode,cx=CX,cy=CY,x=OX,y=OY}=T0) -> {_,X,Y} = wings_wm:local_mouse_state(), DX = X-OX, %since last move X DY = Y-OY, %since last move Y DxOrg = DX+CX, %total X DyOrg = DY+CY, %total Y - wings_io:warp(OX,OY), + T1 = mouse_warp(X, Y, T0), do_tweak_0(DX,DY,DxOrg,DyOrg,Mode), - T = T0#tweak{cx=DxOrg,cy=DyOrg}, + T = T1#tweak{cx=DxOrg,cy=DyOrg}, update_in_drag_radius_handler(T). update_in_drag_radius_handler(T) -> @@ -618,10 +624,10 @@ tweak_magnet_radius_help(Mag), draw_magnet(T), in_drag_radius_no_redraw(T); -handle_in_drag_magnet_ev(#mousemotion{x=X},#tweak{ox=OX, oy=OY}=T0) -> +handle_in_drag_magnet_ev(#mousemotion{x=X, y=Y},#tweak{x=OX}=T0) -> DX = X-OX, %since last move X - wings_io:warp(OX,OY), - T = in_drag_adjust_magnet_radius(DX,T0), + T1 = mouse_warp(X,Y,T0), + T = in_drag_adjust_magnet_radius(DX,T1), update_in_drag_radius_handler(T); handle_in_drag_magnet_ev(#keyboard{sym=Sym,state=?SDL_RELEASED}, #tweak{sym=Sym}=T) -> end_in_drag_mag_event(redraw,T); @@ -648,11 +654,21 @@ wings_wm:later(Ev), pop. - %%% %%% End of event handlers %%% +mouse_warp(_X,_Y,#tweak{warp=true, x=OX,y=OY}=T) -> + wings_io:warp(OX,OY), + T; +mouse_warp(X,Y,#tweak{warp={W,H}}=T) + when X < 10 orelse Y < 10 orelse X > (W-10) orelse Y > (H-10) -> + %% Warp at the window edges + Cx = W div 2, Cy = H div 2, + wings_io:warp(Cx, Cy), + T#tweak{x=Cx,y=Cy}; +mouse_warp(X,Y, T) -> + T#tweak{x=X,y=Y}. redraw(St) -> Render = @@ -1608,7 +1624,7 @@ end, St0), do_tweak_0(0, 0, 0, 0, {move,screen}), {X,Y} = wings_wm:screen2local({X0,Y0}), - update_tweak_handler(T#tweak{mode=NewMode,st=St,ox=X,oy=Y,cx=0,cy=0}); + update_tweak_handler(T#tweak{mode=NewMode,st=St,ox=X,oy=Y,x=X,y=Y,cx=0,cy=0}); _ -> keep end. diff -Nru wings3d-2.2.4/src/wings_tweak_win.erl wings3d-2.2.5/src/wings_tweak_win.erl --- wings3d-2.2.4/src/wings_tweak_win.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_tweak_win.erl 2019-12-07 15:15:57.000000000 +0000 @@ -96,7 +96,7 @@ -record(state, {me, name, shown, mode, menu, prev, cols}). init([Frame, Name, {Mode, Menus}]) -> - Panel = wxPanel:new(Frame, [{style, ?wxBORDER_SIMPLE}]), + Panel = wxPanel:new(Frame, [{style, wings_frame:get_border()}]), HotKeys = wings_hotkey:matching([tweak]), Entries0 = [wings_menu:normalize_menu_wx(Entry, HotKeys, [tweak]) || Entry <- lists:flatten(Menus)], @@ -116,21 +116,30 @@ wxSizer:add(Main, Sizer, [{proportion, 1}, {border, 5}, {flag, ?wxALL}]), wxSizer:addSpacer(Main, 5), wxPanel:setSizer(Panel, Main), - wxPanel:connect(Panel, enter_window), + All = all_children([Panel], []), + [wxPanel:connect(C, enter_window) || C <- All], + [wxPanel:connect(C, left_up) || C <- All], wxSizer:fit(Main, Panel), ?GET(top_frame) =:= Frame orelse wxSizer:setSizeHints(Main, Frame), {Panel, #state{me=Panel, name=Name, shown=Entries, cols=Cols, mode=Mode, menu=Menus}}. +all_children([Panel|Rest], Acc0) -> + Chs = wxWindow:getChildren(Panel), + Acc = all_children(Chs, [Panel|Acc0]), + all_children(Rest, Acc); +all_children([], Acc) -> + Acc. + handle_event(#wx{id=Id, obj=Obj, event=#wxMouse{type=enter_window}}=Ev, #state{me=Me, name=Name, shown=Entries, prev=_Prev} = State) -> case wings_util:wxequal(Obj, Me) of - true -> wings_status:message(Name, ""); - false -> wings_status:message(Name, wings_menu:entry_msg(Id, Entries)) + false when Id > 0 -> wings_status:message(Name, wings_menu:entry_msg(Id, Entries)); + _ -> wings_status:message(Name, "") end, wings_frame ! Ev#wx{userData={win, Me}}, {noreply, State#state{prev=line}}; handle_event(#wx{id=Id, event=#wxMouse{}=ME}, - #state{name=Name, shown=Entries} = State) -> + #state{name=Name, shown=Entries} = State) when Id > 0 -> Cmd = case wings_menu:entry_cmd(Id, Entries) of {_, {Mode, _}} -> tweak_mode_cmd(Mode, ME); Command -> Command @@ -139,7 +148,7 @@ wings_wm:psend(Name, {apply, true, Do}), {noreply, State}; handle_event(#wx{} = _Ev, State) -> - %% io:format("~p:~p Got unexpected event ~p~n", [?MODULE,?LINE, Ev]), + %% ?dbg("Got unexpected event ~p~n", [_Ev]), {noreply, State}. %%%%%%%%%%%%%%%%%%%%%% diff -Nru wings3d-2.2.4/src/wings_wm.erl wings3d-2.2.5/src/wings_wm.erl --- wings3d-2.2.4/src/wings_wm.erl 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/src/wings_wm.erl 2019-12-07 15:15:57.000000000 +0000 @@ -30,7 +30,7 @@ %% Window information. -export([top_size/0, - viewport/0, viewport/1, + viewport/0, viewport/1, win_scale/0, win_size/0, win_size/1, win_rect/0, win_rect/1, win_ul/0, win_ul/1, win_ur/1,win_ll/1,win_lr/1,win_z/1 @@ -43,9 +43,6 @@ %% Setting the information bar message. -export([message/1,message/2,message_right/1]). -%% Drag & Drop support. --export([drag/3,drag/4,allow_drag/1]). - %% Window property mangagement. -export([get_props/1,get_prop/1,get_prop/2,lookup_prop/1,lookup_prop/2, set_win_props/2, set_prop/2,set_prop/3,erase_prop/1,erase_prop/2, @@ -70,7 +67,8 @@ stk, %Event handler stack. links=[], %Windows linked to this one. dd=none, %Display data cache - obj %wx window + obj, %wx window + scale=1 %HighDpi scale factor }). -record(se, %Stack entry record. @@ -131,9 +129,10 @@ new_props(Name, [{font,system_font}]), Z = highest_z(), {W,H} = wxWindow:getClientSize(Obj), - %% io:format("~p:~p: ~p ~p~n",[?MODULE, ?LINE, Name, Obj]), + Scale = wxWindow:getContentScaleFactor(Obj), + %% io:format("~p:~p: ~p ~p ~p~n",[?MODULE, ?LINE, Name, Obj, Scale]), use_opengl(Obj) andalso init_opengl(Name, Obj), - Win = #win{x=0,y=0,z=Z,w=W,h=H,name=Name,stk=Stk,obj=Obj}, + Win = #win{x=0,y=0,z=Z,w=W,h=H,name=Name,stk=Stk,obj=Obj, scale=Scale}, put(wm_windows, gb_trees:insert(Name, Win, get(wm_windows))), put(Obj, Name), dirty(). @@ -237,7 +236,10 @@ new_resolve_z(Z) when is_integer(Z), Z >= 0-> Z. delete(Name) -> - wings_frame:close(wxwindow(Name)), + case wxwindow(Name) of + undefined -> ignore; + Win -> wings_frame:close(Win) + end, Windows = delete_windows(Name, get(wm_windows)), put(wm_windows, Windows), case is_window(grabbed_focus_window()) of @@ -347,6 +349,7 @@ case this() of geom -> true; {geom,_} -> true; + {plugin, {_, geom}} -> true; _ -> false end. @@ -496,6 +499,10 @@ viewport() -> get(wm_viewport). +win_scale() -> + #win{scale=Scale} = get_window_data(this()), + Scale. + viewport(Name) -> #win{x=X,y=Y0,w=W,h=H} = get_window_data(Name), {_,TopH} = get(wm_top_size), @@ -572,13 +579,30 @@ end. set_value(Key,Val) -> - put(Key, Val), + case cache_key(Key) of + true -> + %% (self() =:= whereis(wings)) orelse + %% io:format("Cached value (~p) updated from wrong process~n",[Key]), + put(Key, Val); + false -> ok + end, wings_pref:set_value({temp, Key}, Val). delete_value(Key) -> erase(Key), wings_pref:delete_value({temp, Key}). + +cache_key(cursors) -> true; +cache_key(wings_plugins) -> true; +cache_key(gl_canvas) -> true; +cache_key(top_frame) -> true; +cache_key(light_shaders) -> true; +cache_key(system_font) -> true; +cache_key({geom, _}) -> true; %% Only set by wings_wm? +cache_key({_, props}) -> true; %% Only set by wings_wm? +cache_key(_) -> false. + new_props(Win, Props0) -> Props = gb_trees:from_orddict(Props0), ?SET({Win, props}, Props). @@ -714,6 +738,12 @@ dirty(), true; false -> + case get_focus_window() of + {grabbed, Grab} -> + do_dispatch(Grab, lost_focus); + _ -> + ignore + end, update_focus(none), true end; @@ -747,7 +777,8 @@ {{grabbed, OtherWin}, Win} -> case OtherWin of dialog_blanket -> ignore; - _ -> ?dbg("Grabbed focus lost old?: ~p ~p~n", [OtherWin, Win]) + _ -> + ?dbg("Grabbed focus lost old?: ~p ~p ~p~n", [OtherWin, Win, New]) end, ok; {_OldFocus, _CurrentFocus} -> @@ -871,7 +902,7 @@ wings_io:set_cursor(get(wm_cursor)), event_loop(). -redraw_win({Name, #win{w=W,h=H,obj=Obj}}) -> +redraw_win({Name, #win{w=W,h=H,obj=Obj,scale=Scale}}) -> DoSwap = case use_opengl(Obj) of true -> case get(current_gl) of @@ -880,7 +911,7 @@ wxGLCanvas:setCurrent(Obj), put(current_gl, Obj) end, - gl:viewport(0,0,W,H), + gl:viewport(0,0,round(W*Scale),round(H*Scale)), gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT), true; false -> @@ -1124,7 +1155,15 @@ dirty() end; wm_event({callback,Cb}) -> - Cb(). + Cb(); +wm_event({drop, GlobalPos, Drop}) -> + case window_below(GlobalPos) of + none -> + ok; + Win -> + send(Win, {drop, Drop}), + ok + end. %%% %%% Finding the active geom window, wings_wm only handles focus of geom windows @@ -1136,8 +1175,6 @@ Focus -> Focus end. -window_below(X,Y) -> - window_below({X,Y}). window_below(Pos) -> Win0 = wx_misc:findWindowAtPoint(Pos), case wx:is_null(Win0) of @@ -1179,120 +1216,6 @@ end. %%% -%%% Drag and drop support. -%%% --record(drag, - {data, %Drop data. - bstate, %State of mouse buttons. - redraw, %Redraw function. - over=none, %We are over this window. - drop_ok=false %Drop is OK on this window. - }). - -allow_drag(false) -> allow_drag_1(arrow); -allow_drag(true) -> allow_drag_1(pointing_hand). - -allow_drag_1(Cursor) -> - case get(wm_cursor) of - Cursor -> ok; - _ -> - put(wm_cursor, Cursor), - wings_io:set_cursor(Cursor) - end. - -drag(Ev, Rect, DropData) -> - Redraw = fun() -> - gl:pushAttrib(?GL_POLYGON_BIT bor ?GL_LINE_BIT), - gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_LINE), - gl:lineStipple(2, 2#0101010101010101), - gl:enable(?GL_LINE_STIPPLE), - gl:color3b(0, 0, 0), - {W,H} = wings_wm:win_size(), - gl:rectf(0.5, 0.5, W-1, H-1), - gl:popAttrib() - end, - drag(Ev, Rect, Redraw, DropData). - -drag(#mousemotion{x=X,y=Y,state=State}, Rect, Redraw, DropData) -> - drag_1(X, Y, State, Rect, Redraw, DropData); -drag(#mousebutton{x=X,y=Y,button=B}, Rect, Redraw, DropData) -> - State = 1 bsl (B-1), - drag_1(X, Y, State, Rect, Redraw, DropData). - -drag_1(X1, Y1, State, {W,H}, Redraw, DropData) -> - X = X1 - W div 2, - Y = Y1 - H div 2, - Drag = #drag{data=DropData,bstate=State,redraw=Redraw}, - Op = {seq,push,get_drag_event(Drag)}, - Name = dragger, - new(Name, {X,Y,highest}, {W,H}, Op), - grab_focus(Name), - dirty(), - keep. - -get_drag_event(Drag) -> - {replace,fun(Ev) -> drag_event(Ev, Drag) end}. - -drag_event(redraw, #drag{redraw=Redraw}) -> - wings_io:ortho_setup(), - Redraw(), - keep; -drag_event(#mousemotion{x=X0,y=Y0}, #drag{over=Over0}=Drag0) -> - {W,H} = wings_wm:win_size(), - offset(dragger, X0 - W div 2, Y0 - H div 2), - {X,Y} = local2screen({X0, Y0}), - hide(dragger), - Over = window_below(X, Y), - show(dragger), - case Over of - Over0 -> keep; - _ -> - Drag = Drag0#drag{over=Over}, - drag_filter(Drag) - end; -drag_event(#mousebutton{button=B,state=?SDL_RELEASED}, - #drag{bstate=State,data=DropData,drop_ok=DropOK}) -> - if - ((1 bsl (B-1)) band State) =/= 0 -> - case DropOK of - false -> ok; - true -> - {X,Y,W,H} = wings_wm:win_rect(dragger), - Ev = {drop,{X + W div 2,Y + H div 2},DropData}, - wings_io:putback_event(Ev) - end, - put(wm_cursor, arrow), - delete; - true -> keep - end; -drag_event(_, _) -> keep. - -drag_filter(#drag{over=none}=Drag) -> - stop_cursor(Drag); -drag_filter(#drag{over=Win,data=DropData}=Drag) -> - case lookup_prop(Win, drag_filter) of - {value,Fun} when is_function(Fun) -> - case Fun(DropData) of - yes -> - message(""), - put(wm_cursor, closed_hand), - get_drag_event(Drag#drag{drop_ok=true}); - {yes,Message} -> - message(Message), - put(wm_cursor, closed_hand), - get_drag_event(Drag#drag{drop_ok=true}); - no -> - stop_cursor(Drag) - end; - none -> stop_cursor(Drag) - end. - -stop_cursor(Drag) -> - put(wm_cursor, stop), - message(""), - get_drag_event(Drag#drag{drop_ok=false}). - -%%% %%% Utility functions. %%% Binary files /tmp/tmpn2Khl7/RiYg65ZFYh/wings3d-2.2.4/textures/about_wings_art.png and /tmp/tmpn2Khl7/z1ttp1V1ss/wings3d-2.2.5/textures/about_wings_art.png differ Binary files /tmp/tmpn2Khl7/RiYg65ZFYh/wings3d-2.2.4/textures/brand.png and /tmp/tmpn2Khl7/z1ttp1V1ss/wings3d-2.2.5/textures/brand.png differ diff -Nru wings3d-2.2.4/tools/release wings3d-2.2.5/tools/release --- wings3d-2.2.4/tools/release 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/tools/release 2019-12-07 15:15:57.000000000 +0000 @@ -47,7 +47,7 @@ [_|_] -> fatal("Directory \"~s\" is not empty\n", [Build]) end, - case os:type() of + case os_type() of {unix,darwin} -> mac_release(Build, WingsVsn); {unix,_} -> @@ -72,6 +72,7 @@ [copy(F, Resources) || F <- ResourceFiles], copy("InfoPlist.strings", filename:join(Resources, "English.lproj")), release_otp(Resources, undefined), + convert_script(Resources), run("codesign", ["-s","Developer ID",Root]), ok = file:set_cwd(lib_dir(wings)), MakeDmg = filename:join([lib_dir(wings),"macosx","make_dmg"]), @@ -80,7 +81,7 @@ unix_release(BuildRoot, WingsVsn) -> UnixDir = filename:join(lib_dir(wings), "unix"), - {unix,Flavor} = os:type(), + {unix,Flavor} = os_type(), Name = "wings-" ++ WingsVsn ++ "-" ++ atom_to_list(Flavor), Root = filename:join(BuildRoot, Name), release_otp(Root, undefined), @@ -93,13 +94,20 @@ ok = file:write_file(Installer, Install), ok = file:write_file_info(Installer, #file_info{mode=8#555}), MakeSelf = filename:join(UnixDir, "makeself.sh"), + convert_script(Root), ok = file:set_cwd(lib_dir(wings)), - run(MakeSelf, ["--bzip2",Root,Name++".bzip2.run","Wings3D", - "./install_wings",WingsVsn]), + run(MakeSelf, ["--bzip2",Root,Name++".bzip2.run","Wings3D","./install_wings"]), ok. win_release(Root, WingsVsn) -> - BitSize = erlang:system_info(wordsize)*8, + BitSize = case is_wslcross() of + true -> %% We use the native erlang to decide bitsize + WsStr = os:cmd("erl.exe -eval \"erlang:display(erlang:system_info(wordsize))\" -run erlang halt"), + {WS, _} = string:to_integer(WsStr), + WS*8; + _ -> + erlang:system_info(wordsize)*8 + end, release_otp(Root, BitSize), VcVersion = setup_vcredist(BitSize, Root), WinDir = filename:join(lib_dir(wings), "win32"), @@ -108,24 +116,38 @@ "install.ico", "uninstall.ico"], [copy(File, Root) || File <- WinFiles], check_build_target("Wings3D.exe", BitSize), + convert_script(Root), ok = file:set_cwd(Root), - run(makensis, ["/DREDIST_DLL_VERSION="++VcVersion, - "/DWINGS_VERSION="++WingsVsn, - nsi_file(BitSize)]), + run("makensis.exe", ["/DREDIST_DLL_VERSION="++VcVersion, + "/DWINGS_VERSION="++WingsVsn, + nsi_file(BitSize)]), ok. setup_vcredist(BitSize, Root) -> - VCRedist = os:getenv("WINGS_VCREDIST"), - (VCRedist == false) andalso fatal("No WINGS_VCREDIST in env", []), - (ok == file:set_cwd(filename:dirname(VCRedist))) orelse - fatal("dirname(WINGS_VCREDIST) failed: ~ts~n",[VCRedist]), + VCRedist0 = os:getenv("WINGS_VCREDIST"), + (VCRedist0 == false) andalso fatal("No WINGS_VCREDIST in env", []), + (ok == file:set_cwd(filename:dirname(VCRedist0))) orelse + fatal("dirname(WINGS_VCREDIST) failed: ~ts~n",[VCRedist0]), {File, Vsn} = case BitSize of 32 -> {"vcredist_x86.exe", ?WIN32_VCREDIST_VERSION}; 64 -> {"vcredist_x64.exe", ?WIN64_VCREDIST_VERSION} end, - Quoted0 = filename:join(filename:dirname(VCRedist), File), - Quoted = "\"" ++ string:join(string:tokens(Quoted0, "/"), "\\\\") ++ "\"", - Res0 = os:cmd("wmic datafile where name=" ++ Quoted ++ " get version"), + + Quoted = case is_wslcross() of + true -> + VCRedist = string:chomp(os:cmd("wslpath -m " ++ VCRedist0)), + case filename:basename(VCRedist0) of + File -> ok; + _File -> io:format("Wrong vcredist file: ~p expected ~p~n",[_File, File]), + exit({bad, vcredist_file}) + end, + "\\'" ++ string:join(string:tokens(VCRedist, "/"), "\\\\\\\\") ++ "\\'"; + _ -> + Quoted0 = filename:join(filename:dirname(VCRedist0), File), + "\"" ++ string:join(string:tokens(Quoted0, "/"), "\\\\") ++ "\"" + end, + CMD = "wmic.exe datafile where name=" ++ Quoted ++ " get version", + Res0 = os:cmd(CMD), VcVsn = [list_to_integer(V) || V <- tl(string:tokens(Res0, ".\s\r\n"))], redist_version_ok(VcVsn, [list_to_integer(V) || V <- string:tokens(Vsn, ".")]), copy(File, Root), @@ -140,6 +162,19 @@ nsi_file(32) -> "wings.nsi"; nsi_file(64) -> "wings64.nsi". +convert_script(Root) -> + ok = file:set_cwd(filename:join(lib_dir(wings), src)), + copy("wings_convert.escript", Root), + {Escript, Convert} = + case os_type() of + {win32,nt} -> {"escript.exe", "wings_convert.exe"}; + _ -> {"escript", "wings_convert"} + end, + %% Rename escript to wings_convert, which fixes the problem + %% of finding the escript executable from the escript file. + ok = file:rename(filename:join([Root, bin, Escript]), + filename:join(Root, Convert)). + release_otp(TargetDir0, BitSize) -> TargetDir = filename:absname(TargetDir0), Lib = filename:join(TargetDir, "lib"), @@ -152,20 +187,26 @@ "18" -> ["child_setup"]; _ -> ["erl_child_setup"] end, - Files = case os:type() of + Files = case os_type() of {unix,_} -> %% Do not copy to erts-VSN/bin, then the start scripts must find %% erts-VSN in start script - {["beam.smp","erlexec","inet_gethost"|VDeps], []}; + {["beam.smp","erlexec","inet_gethost","escript"|VDeps], []}; {win32,nt} -> %% To get Windows working without install, erts-VSN must exist - {["erl.exe", "werl.exe"], + {["erl.exe", "werl.exe", "escript.exe"], ["erlexec.dll", "beam.smp.dll","inet_gethost.exe"]} end, copy_erts(TargetDir, Files). copy_erts(TargetDir, {BinExecFiles, ErtsExecFiles}) -> - ErtsPath0 = filename:join([code:root_dir(),"erts-*"]), + Root = case is_wslcross() of + true -> + WinPath = os:cmd("erl.exe -eval \"erlang:display(code:root_dir())\" -run erlang halt"), + string:chomp(os:cmd("wslpath -u " ++ WinPath)); + false -> code:root_dir() + end, + ErtsPath0 = filename:join([Root,"erts-*"]), ErtsPath = filelib:wildcard(ErtsPath0), %% Hard code erts version 0 so that we do not need to figure @@ -180,6 +221,7 @@ [copy(File, TargetBin) || File <- BinExecFiles], ok = file:set_cwd(filename:join(code:root_dir(), "bin")), copy("start.boot", TargetBin), + copy("no_dot_erlang.boot", TargetBin), ok. copy_app(App0, Lib, BitSize) -> @@ -210,23 +252,21 @@ Target = filename:join(Target0, Source), ok = filelib:ensure_dir(Target), try - case filename:extension(Source) of - ".beam" -> + case ext(filename:extension(Source)) of + {binary,beam} -> {ok,Beam} = file:read_file(Source), {ok,{_,Stripped}} = beam_lib:strip(Beam), ok = file:write_file(Target, Stripped), %% Only allow read access. ok = file:write_file_info(Target, #file_info{mode=8#444}); - ".lang" -> - {ok,_} = file:copy(Source, Target), - ok = file:write_file_info(Target, #file_info{mode=8#444}); - ".png" -> + {text,_} -> {ok,_} = file:copy(Source, Target), ok = file:write_file_info(Target, #file_info{mode=8#444}); - ".app" -> + {image, _} -> {ok,_} = file:copy(Source, Target), ok = file:write_file_info(Target, #file_info{mode=8#444}); - Ext -> + + {_, Ext} -> %% Could be an executable file. Make sure that we preserve %% the execution bit. Always turn off the write bit. {ok,_} = file:copy(Source, Target), @@ -235,7 +275,7 @@ Info = Info0#file_info{mode=Mode band 8#555}, ok = file:write_file_info(Target, Info), case Mode band 8#111 of - 0 when Ext =/= ".dll" -> + 0 when Ext =/= dll -> ok; _ -> %% Strip all executable files. @@ -248,6 +288,22 @@ end, ok. +%% WSLcross have no idea about file-modes explicit check extensions +ext(".beam") -> {binary, beam}; +ext(".png") -> {image, png}; +ext(".bin") -> {image, bin}; +ext(".bundle") -> {image, bundle}; +ext(".lang") -> {text, lang}; +ext(".app") -> {text, app}; +ext(".cl") -> {text, shader}; +ext(".glsl") -> {text, shader}; +ext(".fs") -> {text, shader}; +ext(".vs") -> {text, shader}; +ext(".auv") -> {text, shader}; +ext("." ++ Rest) -> {unknown, list_to_atom(Rest)}; +ext("") -> {unknown, []}. + + fix_app_version(App0, Lib) -> AppDir = lib_dir(App0), App = atom_to_list(App0), @@ -268,7 +324,7 @@ end. strip(File) -> - case os:type() of + case os_type() of {unix,darwin} -> os:cmd("strip -S " ++ File); {unix,linux} -> @@ -320,7 +376,9 @@ end. wings_apps() -> - [cl,kernel,stdlib,wings,wx,xmerl]. + %% compiler is needed for the wings_convert script + %% adds 0.5 meg to the installer + [cl,kernel,stdlib,wings,wx,xmerl,compiler]. check_build_target(_File, undefined) -> true; check_build_target(File, Bits) -> @@ -330,16 +388,46 @@ _ -> error({badtarget, Bits, File}) end. +os_type() -> + case os:type() of + {unix,linux} = Linux -> + case is_wslcross() of + true -> {win32,nt}; + _ -> Linux + end; + Native -> + Native + end. + +is_wslcross() -> + case get(wslcross) of + undefined -> + Res = case os:getenv("WSLcross") of + "true" -> true; + _ -> false + end, + put(wslcross, Res), + Res; + Res -> + Res + end. + fatal(Format, Args) -> throw({fatal,["release: "|io_lib:format(Format, Args)]}). - lib_dir(App) -> - case code:lib_dir(App) of - {error,_} -> %% ERL_LIBS is not set - case App of - wings -> get(top_dir); - cl -> filename:join([get(top_dir), "_deps", "cl"]) - end; - Dir -> Dir + case is_wslcross() of + true when App /= wings, App /= cl -> + Str = io_lib:format("erl.exe -eval \"erlang:display(code:lib_dir(~s))\" -run erlang halt", [App]), + WinPath = os:cmd(lists:flatten(Str)), + string:chomp(os:cmd("wslpath -u " ++ WinPath)); + _ -> + case code:lib_dir(App) of + {error,_} -> %% ERL_LIBS is not set + case App of + wings -> get(top_dir); + cl -> filename:join([get(top_dir), "_deps", "cl"]) + end; + Dir -> Dir + end end. diff -Nru wings3d-2.2.4/.travis.yml wings3d-2.2.5/.travis.yml --- wings3d-2.2.4/.travis.yml 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/.travis.yml 2019-12-07 15:15:57.000000000 +0000 @@ -33,7 +33,7 @@ after_success: - dialyzer --build_plt --apps erts kernel stdlib wx tools xmerl cl --statistics - - dialyzer -n -Wno_improper_lists ebin/*.beam plugins/accel/*.beam plugins/autouv/*.beam plugins/commands/*.beam plugins/default/*.beam plugins/import_export/*.beam plugins/primitives/*.beam --statistics + - dialyzer -n -Wno_improper_lists ebin/*.beam plugins/autouv/*.beam plugins/commands/*.beam plugins/default/*.beam plugins/import_export/*.beam plugins/primitives/*.beam --statistics # Needs a compiled cl-1.2.3 after_script: diff -Nru wings3d-2.2.4/unix/install_wings.src wings3d-2.2.5/unix/install_wings.src --- wings3d-2.2.4/unix/install_wings.src 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/unix/install_wings.src 2019-12-07 15:15:57.000000000 +0000 @@ -3,7 +3,12 @@ # $Id$ # WINGS_VSN="%WINGS_VSN%" -INSTALL_DIR="$HOME/wings-$WINGS_VSN" + +if test x"$1" = x"" ; then + INSTALL_DIR="$HOME/wings-$WINGS_VSN" +else + INSTALL_DIR="$1/wings-$WINGS_VSN" +fi echo "Installing Wings 3D $WINGS_VSN in $INSTALL_DIR" mkdir -p $INSTALL_DIR @@ -28,6 +33,24 @@ chmod +x $INSTALL_DIR/wings +sed <<'EOF' -e "s^%ROOTDIR%^$INSTALL_DIR^" > $INSTALL_DIR/bin/erl +#!/bin/sh +ROOTDIR=%ROOTDIR% +BINDIR=$ROOTDIR/bin +EMU=beam +PROGNAME=`echo $0 | sed 's/.*\///'` +unset ERL_LIBS +export EMU +export ROOTDIR +export BINDIR +export PROGNAME +GDK_BACKEND=x11 +export GDK_BACKEND +exec "$BINDIR/erlexec" ${1+"$@"} +EOF + +chmod +x $INSTALL_DIR/bin/erl + sed <<'EOF' -e "s^%ROOTDIR%^$INSTALL_DIR^;s^%WINGS_VSN%^$WINGS_VSN^" > $INSTALL_DIR/wings.desktop [Desktop Entry] Type=Application diff -Nru wings3d-2.2.4/unix/wings.appdata.xml wings3d-2.2.5/unix/wings.appdata.xml --- wings3d-2.2.4/unix/wings.appdata.xml 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/unix/wings.appdata.xml 2019-12-07 15:15:57.000000000 +0000 @@ -30,6 +30,7 @@ http://www.wings3d.com/wp-content/uploads/linux-screenshot.png + diff -Nru wings3d-2.2.4/version wings3d-2.2.5/version --- wings3d-2.2.4/version 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/version 2019-12-07 15:15:57.000000000 +0000 @@ -1 +1 @@ -2.2.4 +2.2.5 diff -Nru wings3d-2.2.4/win32/Makefile wings3d-2.2.5/win32/Makefile --- wings3d-2.2.4/win32/Makefile 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/win32/Makefile 2019-12-07 15:15:57.000000000 +0000 @@ -4,7 +4,7 @@ # Makefile for building the Wings3D.exe wrapper # for starting Wings. # -# Copyright (c) 2003-2009 Bjorn Gustavsson +# Copyright (c) 2003-2009 Bjorn Gustavsson & Dan Gudmundsson # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -13,16 +13,30 @@ # LIBS = -lkernel32 -luser32 -ladvapi32 +CFLAGS = -mwindows +OUT = -o +RES_FILE = wings3d_res.o ifeq ($(findstring CYGWIN,$(shell uname -s)),CYGWIN) - GCC = mingw32-gcc + CC = mingw32-gcc else - GCC = gcc + ifeq ($(WSLcross), true) + OUT = -nologo -Fe + CC = cl.exe + RES_FILE = wings3d.res + CFLAGS = + LIBS = /link Shell32.lib user32.lib kernel32.lib advapi32.lib + else + CC = gcc + endif endif -Wings3D.exe: wings3d.c wings3d_res.o - $(GCC) -o Wings3D.exe wings3d.c wings3d_res.o -mwindows $(LIBS) +Wings3D.exe: wings3d.c $(RES_FILE) + $(CC) $(OUT)Wings3D.exe wings3d.c $(RES_FILE) $(CFLAGS) $(LIBS) wings3d_res.o: wings3d.rc windres -i wings3d.rc -o wings3d_res.o + +wings3d.res: wings3d.rc + rc.exe wings3d.rc diff -Nru wings3d-2.2.4/win32/SetupWSLcross.bat wings3d-2.2.5/win32/SetupWSLcross.bat --- wings3d-2.2.4/win32/SetupWSLcross.bat 1970-01-01 00:00:00.000000000 +0000 +++ wings3d-2.2.5/win32/SetupWSLcross.bat 2019-12-07 15:15:57.000000000 +0000 @@ -0,0 +1,61 @@ +@echo off +rem Setup MCL and echo the environment +rem Usage: eval `cmd.exe /c SetupWSLcross.bat x64` + +IF "%~1"=="x86" GOTO search +IF "%~1"=="x64" GOTO search + +GOTO badarg + +:search +IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat". ( + call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" %~1 > nul + goto continue +) + +IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat". ( + call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" %~1 > nul + goto continue +) + +IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat". ( + call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" %~1 > nul + goto continue +) + +IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat". ( + call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %~1 > nul + goto continue +) + +GOTO no_vcvars + +:continue +FOR /F "delims==" %%F IN ('where cl.exe') DO SET _cl_exec_=%%F +FOR %%F IN ("%_cl_exec_%") DO SET CL_PATH=%%~dpF + +FOR /F "delims==" %%F IN ('where rc.exe') DO SET _rc_exec_=%%F +FOR %%F IN ("%_rc_exec_%") DO SET RC_PATH=%%~dpF + +rem Order is important for some unknown reason +set WSLENV=VCToolsRedistDir/up:CL_PATH/up:RC_PATH/up:LIBPATH/ul:LIB/ul:INCLUDE/ul +wsl.exe echo INCLUDE=\"$INCLUDE\"; +wsl.exe echo LIB=\"$LIB\"; +wsl.exe echo LIBPATH=\"$LIBPATH\"; +wsl.exe echo VCToolsRedistDir=\"$VCToolsRedistDir\"; +wsl.exe echo PATH=\"$CL_PATH\":\"$RC_PATH\":'$PATH'; +wsl.exe echo WSLENV='$WSLENV:LIBPATH/l:LIB/l:INCLUDE/l'; +wsl.exe echo WSLcross=true; +wsl.exe echo export 'INCLUDE LIB LIBPATH VCToolsRedistDir WSLENV PATH WSLcross'; +wsl.exe echo "# Eval this file eval \`cmd.exe /c SetupWSLcross.bat\`" + +rem done +exit + +:badarg +echo "Bad TARGET or not specified: %~1 expected x86 or x64" +exit + +:no_vcvars +echo "Error: SetupWSLcross.bat: Could not find vcvarsall.bat" +exit Binary files /tmp/tmpn2Khl7/RiYg65ZFYh/wings3d-2.2.4/win32/wings3d.res and /tmp/tmpn2Khl7/z1ttp1V1ss/wings3d-2.2.5/win32/wings3d.res differ diff -Nru wings3d-2.2.4/win32/wings64.nsi wings3d-2.2.5/win32/wings64.nsi --- wings3d-2.2.4/win32/wings64.nsi 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/win32/wings64.nsi 2019-12-07 15:15:57.000000000 +0000 @@ -101,6 +101,9 @@ SetOutPath "$INSTDIR" File /r Wings3D.exe + File /r wings_convert.exe + File /r wings_convert.escript + SetOutPath "$INSTDIR\lib" File /r lib\*.* SetOutPath "$INSTDIR\bin" @@ -264,6 +267,9 @@ Delete "$INSTDIR\AUTHORS" Delete "$INSTDIR\license.terms" Delete "$INSTDIR\Wings3D.exe" + Delete "$INSTDIR\wings_convert.exe" + Delete "$INSTDIR\wings_convert.escript" + Delete "$INSTDIR\wings_crash.dump" SetShellVarContext All ;MessageBox MB_OK "$DESKTOP\Wings 3D ${WINGS_VERSION}.lnk" diff -Nru wings3d-2.2.4/win32/wings.nsi wings3d-2.2.5/win32/wings.nsi --- wings3d-2.2.4/win32/wings.nsi 2019-04-06 15:47:18.000000000 +0000 +++ wings3d-2.2.5/win32/wings.nsi 2019-12-07 15:15:57.000000000 +0000 @@ -100,6 +100,8 @@ SetOutPath "$INSTDIR" File /r Wings3D.exe + File /r wings_convert.exe + File /r wings_convert.escript SetOutPath "$INSTDIR\lib" File /r lib\*.* SetOutPath "$INSTDIR\bin" @@ -263,6 +265,9 @@ Delete "$INSTDIR\AUTHORS" Delete "$INSTDIR\license.terms" Delete "$INSTDIR\Wings3D.exe" + Delete "$INSTDIR\wings_convert.exe" + Delete "$INSTDIR\wings_convert.escript" + Delete "$INSTDIR\wings_crash.dump" SetShellVarContext All ;MessageBox MB_OK "$DESKTOP\Wings 3D ${WINGS_VERSION}.lnk"