From f50e32facccbcc77a39effacc350a0bba94e4a67 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 00:12:42 +0200 Subject: [PATCH 01/11] feat(nft): import NFT tracking helpers from jsartoolkitNFT (#75) Copy trackingMod, trackingMod2d, trackingSub, markerDecompress and NFTMarkerState from jsartoolkitNFT/emscripten (master) into WebARKit/WebARKitTrackers/WebARKitNFT. Local includes now use . No functional changes; build integration and fixes follow in separate commits. Co-Authored-By: Claude Opus 5.5 --- .../WebARKitNFT/NFTMarkerState.h | 22 + .../WebARKitNFT/markerDecompress.h | 21 + .../WebARKitNFT/trackingMod.h | 83 ++ .../WebARKitNFT/trackingSub.h | 95 ++ .../WebARKitNFT/markerDecompress.c | 201 ++++ .../WebARKitNFT/trackingMod.c | 908 ++++++++++++++++++ .../WebARKitNFT/trackingMod2d.c | 216 +++++ .../WebARKitNFT/trackingSub.c | 230 +++++ 8 files changed, 1776 insertions(+) create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/NFTMarkerState.h create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingSub.h create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/NFTMarkerState.h b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/NFTMarkerState.h new file mode 100644 index 0000000..3278b42 --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/NFTMarkerState.h @@ -0,0 +1,22 @@ +#ifndef NFT_MARKER_STATE_H +#define NFT_MARKER_STATE_H + +#include +#include + +/** + * Tracking state for one NFT marker (page). + * + * The bindings keep one of these per loadable page (PAGES_MAX). It replaces the + * single `detectedPage` / `ftmi` pair that limited tracking to one marker at a + * time (#613, #611). + */ +struct NFTMarkerState { + bool tracking = false; // AR2 is locked on this page + ARdouble pose[3][4] = {}; // latest pose, filtered when filtering is on + float err = -1.0f; // AR2 tracking error for `pose` + ARFilterTransMatInfo *ftmi = nullptr; // this marker's pose filter, created on first use + bool filterNeedsReset = true; // restart the filter on (re)acquisition +}; + +#endif // NFT_MARKER_STATE_H diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h new file mode 100644 index 0000000..25bf987 --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h @@ -0,0 +1,21 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + char *iset_content; + char *fset_content; + char *fset3_content; +} markerContentStruct; + +char* nameConcat(const char *s1, const char *s2); +FILE *openZFT( const char *filename, const char *ext); +int decompressMarkers(const char* src, const char* outTemp); +void extractDataAndSave(const char* str, const char* name); + +#ifdef __cplusplus +} +#endif diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h new file mode 100644 index 0000000..28a9d15 --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h @@ -0,0 +1,83 @@ +/* + * trackingMod.h modified version of ar2Tracking() + * from AR2/tracking.c + * ARToolKit5 + * + * This file is part of ARToolKit. + * + * ARToolKit is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARToolKit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ARToolKit. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2015 Daqri, LLC. + * Copyright 2006-2015 ARToolworks, Inc. + * + * Author(s): Hirokazu Kato, Philip Lamb + * Mod. by Walter Perdan @kalwalt + * + */ +/*! + @header tracking + @abstract ARToolKit NFT core routines. + @discussion + This header declares essential types and API for the NFT portion of the + ARToolKit SDK. + + For compile-time per-machine and NFT configuration, see <AR2/config.h>. + @copyright 2015 Daqri, LLC. + */ + +#ifndef __trackingMod_H__ +#define __trackingMod_H__ +#include +#include +#include +#include +#include +#include +#include + +#define AR2_TRACKING_6DOF 1 +#define AR2_TRACKING_HOMOGRAPHY 2 + +#ifdef __cplusplus +extern "C" { +#endif + +int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, + ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, + AR2Tracking2DResultT *result ); + +AR2HandleT *ar2CreateHandleMod( ARParamLT *cparamLT, AR_PIXEL_FORMAT pixFormat/*, int threadNum*/ ); +AR2HandleT *ar2CreateHandleSubMod( int pixFormat, int xsize, int ysize/*, int threadNum*/ ); + +int ar2DeleteHandleMod( AR2HandleT **ar2Handle ); + +int ar2TrackingMod ( AR2HandleT *ar2Handle, AR2SurfaceSetT *surfaceSet, + ARUint8 *dataPtr, float trans[3][4], float *err ); +int ar2SetInitTrans ( AR2SurfaceSetT *surfaceSet, float trans[3][4] ); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingSub.h b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingSub.h new file mode 100644 index 0000000..2ea09cc --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingSub.h @@ -0,0 +1,95 @@ +/* + * trackingSub.h + * ARToolKit5 + * + * Disclaimer: IMPORTANT: This Daqri software is supplied to you by Daqri + * LLC ("Daqri") in consideration of your agreement to the following + * terms, and your use, installation, modification or redistribution of + * this Daqri software constitutes acceptance of these terms. If you do + * not agree with these terms, please do not use, install, modify or + * redistribute this Daqri software. + * + * In consideration of your agreement to abide by the following terms, and + * subject to these terms, Daqri grants you a personal, non-exclusive + * license, under Daqri's copyrights in this original Daqri software (the + * "Daqri Software"), to use, reproduce, modify and redistribute the Daqri + * Software, with or without modifications, in source and/or binary forms; + * provided that if you redistribute the Daqri Software in its entirety and + * without modifications, you must retain this notice and the following + * text and disclaimers in all such redistributions of the Daqri Software. + * Neither the name, trademarks, service marks or logos of Daqri LLC may + * be used to endorse or promote products derived from the Daqri Software + * without specific prior written permission from Daqri. Except as + * expressly stated in this notice, no other rights or licenses, express or + * implied, are granted by Daqri herein, including but not limited to any + * patent rights that may be infringed by your derivative works or by other + * works in which the Daqri Software may be incorporated. + * + * The Daqri Software is provided by Daqri on an "AS IS" basis. DAQRI + * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + * THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE, REGARDING THE DAQRI SOFTWARE OR ITS USE AND + * OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + * + * IN NO EVENT SHALL DAQRI BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + * MODIFICATION AND/OR DISTRIBUTION OF THE DAQRI SOFTWARE, HOWEVER CAUSED + * AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + * STRICT LIABILITY OR OTHERWISE, EVEN IF DAQRI HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 2015 Daqri LLC. All Rights Reserved. + * Copyright 2010-2015 ARToolworks, Inc. All Rights Reserved. + * + * Author(s): Hirokazu Kato, Philip Lamb. + * + */ + +#ifndef TRACKING_SUB_H +#define TRACKING_SUB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +THREAD_HANDLE_T *trackingInitInit( KpmHandle *kpmHandle ); +int trackingInitStart( THREAD_HANDLE_T *threadHandle, ARUint8 *imagePtrLuma ); +/* Most pages one search can report. Must equal PAGES_MAX in ARToolKitNFT_js_td.h. */ +#define TRACKING_INIT_MAX_RESULTS 20 + +typedef struct { + int page; /* page number of the matched marker */ + float trans[3][4]; /* its initial pose */ + float error; /* KPM pose error */ + int inlierNum; /* KPM inlier count: more means a better match */ +} TrackingInitResult; + +/* + * Collect the result of the search started by trackingInitStart(): every page + * KPM matched, up to maxResults. + * Returns 0 while the search is still running, 1 once it has finished (with + * *resultNum set, possibly to 0), or -1 on error. + */ +int trackingInitGetResults( THREAD_HANDLE_T *threadHandle, TrackingInitResult results[], int maxResults, int *resultNum ); + +/* + * Single-page form, kept for the legacy threaded binding (ARToolKitJS_td.cpp). + * Built on trackingInitGetResults(): reports only the best matched page, the + * one with the most KPM inliers (ties: the lowest KPM error), as the + * single-result KPM chose it. + * Returns 0 while the search is still running, 1 with trans and *page set, or + * -1 if the search matched no page or on error. + */ +int trackingInitGetResult( THREAD_HANDLE_T *threadHandle, float trans[3][4], int *page ); +int trackingInitQuit( THREAD_HANDLE_T **threadHandle_p ); + +#ifdef __cplusplus +} +#endif + +#endif // !TRACKING_SUB_H diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c new file mode 100644 index 0000000..6312467 --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c @@ -0,0 +1,201 @@ +#include +#include + +#ifdef _WIN32 +# include +#else +# include +#endif + +#include +#include +#include +#include +#include +#include + +const int mem_size_4mb = 4*1024*1024; + +int decompressMarkers(const char* src, const char* outTemp){ + // markerContentStruct *markerData; + FILE *fp; + char* in; + char* out; + int filesize, ret; + const char *ext = "zft"; + char *c = malloc (mem_size_4mb); + + fp = openZFT(src, ext); + if ( fp == NULL ) + { + ARLOGe("Error opening .zft file\n"); + exit(EXIT_FAILURE); + } + + fseek (fp, 0, SEEK_END); + filesize = ftell (fp); + fseek (fp, 0, SEEK_SET); + + in = malloc (filesize); + + if (in == NULL) + { + ARLOGe("Error mallocing %i bytes for inflate\n", filesize); + exit(EXIT_FAILURE); + } + ret = fread (in, 1, filesize, fp); + fclose (fp); + char *tempName = nameConcat(src, ext); + remove(tempName); + free(tempName); + + z_stream infstream; + infstream.zalloc = Z_NULL; + infstream.zfree = Z_NULL; + infstream.opaque = Z_NULL; + infstream.avail_in = filesize; + infstream.next_in = (Bytef *) in; + infstream.avail_out = (uInt)mem_size_4mb; + infstream.next_out = (Bytef *) c; + + inflateInit(&infstream); + inflate(&infstream, Z_NO_FLUSH); + inflateEnd(&infstream); + + free(in); + + extractDataAndSave(c, outTemp); + + free(c); + return 0; + // return markerData; +} + +void extractDataAndSave(const char* str, const char* name){ + // string and variable name structure + // + // iset_final_index + // V + // str = {"iset":"test","fset":"test2","fset3":"test3"} + // ∧ + // Beginning of str or iset_initial_index + // + // + // iset_final_index fset_initial_index + // V V + // str = {"iset":"test","fset":"test2","fset3":"test3"} + // ---- <- iset_content + // + + // markerContentStruct *tempMarkerData; + + FILE *tempIset; + FILE *tempFset; + FILE *tempFset3; + + int iset_final_index = 9; + + char *fsetInitialIndex = strstr(str, "\",\"fset\":\""); + if (fsetInitialIndex == NULL) { + ARLOGe("Error: 'fset' not found in the string.\n"); + exit(EXIT_FAILURE); + } + int fset_initial_index = (fsetInitialIndex - str); + + int fset_final_index = (fset_initial_index + 10); + + int iset_content_size = fset_initial_index - iset_final_index; + + char *fset3InitialIndex = strstr(str, "\",\"fset3\":\""); + if (fset3InitialIndex == NULL) { + ARLOGe("Error: 'fset3' not found in the string.\n"); + exit(EXIT_FAILURE); + } + int fset3_initial_index = (fset3InitialIndex - str); + int fset3_final_index = (fset3_initial_index + 11); + + int fset_content_size = fset3_initial_index - fset_final_index; + + char *endOfStr = strstr(str, "\"}"); + if (endOfStr == NULL) { + ARLOGe("Error: end of string not found.\n"); + exit(EXIT_FAILURE); + } + int endPos = endOfStr - str; + + int fset3_content_size = endPos - fset3_final_index; + + // ---ISET--- + if (iset_content_size <= 0) { + ARLOGe("Error: Invalid iset_content_size: %d\n", iset_content_size); + exit(EXIT_FAILURE); + } + char *iset_contentHex = malloc(iset_content_size); + strncpy(iset_contentHex, str + iset_final_index, iset_content_size); + + // tempMarkerData->iset_content = iset_contentHex; + char *isetName = nameConcat(name, ".iset"); + tempIset = fopen(isetName, "w"); + fwrite(iset_contentHex, iset_content_size, 1, tempIset); + // printf(iset_contentHex); + fclose(tempIset); + free(isetName); + free(iset_contentHex); + + // ---FSET--- + char *fset_contentHex = malloc(fset_content_size); + strncpy(fset_contentHex, str + fset_final_index, fset_content_size); + + // tempMarkerData->fset_content = fset_contentHex; + char *fsetName = nameConcat(name, ".fset"); + tempFset = fopen(fsetName, "w"); + fwrite(fset_contentHex, fset_content_size, 1, tempFset); + fclose(tempFset); + free(fsetName); + free(fset_contentHex); + + // ---FSET3--- + char *fset3_contentHex = malloc(fset3_content_size); + strncpy(fset3_contentHex, str + fset3_final_index, fset3_content_size); + + // tempMarkerData->fset3_content = fset3_contentHex; + char *fset3Name = nameConcat(name, ".fset3"); + tempFset3 = fopen(fset3Name, "w"); + fwrite(fset3_contentHex, fset3_content_size, 1, tempFset3); + fclose(tempFset3); + free(fset3Name); + free(fset3_contentHex); + + // return tempMarkerData; +} + +FILE *openZFT( const char *filename, const char *ext) +{ + FILE *fp; + char *buf; + size_t len; + + if (!filename) return (NULL); + if (ext) { + len = strlen(filename) + strlen(ext) + 2; // space for '.' and '\0'. + arMalloc(buf, char, len); + sprintf(buf, "%s.%s", filename, ext); + fp = fopen(buf,"rb"); + free(buf); + } else { + fp = fopen(filename,"rb"); + } + + return fp; +} + +char* nameConcat(const char *s1, const char *s2) +{ + const size_t len1 = strlen(s1); + const size_t len2 = strlen(s2); + char *result = malloc(len1 + len2 + 1); // +1 for the null-terminator + // in real code you would check for errors in malloc here + memcpy(result, s1, len1); + memcpy(result + len1, s2, len2 + 1); // +1 to copy the null-terminator + return result; +} \ No newline at end of file diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c new file mode 100644 index 0000000..bbb7aec --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c @@ -0,0 +1,908 @@ +/* + * trackingMod.c modified version of ar2Tracking() + * from AR2/tracking.c + * ARToolKit5 + * + * This file is part of ARToolKit. + * + * ARToolKit is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARToolKit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ARToolKit. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2015 Daqri, LLC. + * Copyright 2006-2015 ARToolworks, Inc. + * + * Author(s): Hirokazu Kato, Philip Lamb + * Mod. by Walter Perdan @kalwalt + * + */ + #include + #include + #include + #include + #ifndef _WIN32 + #include + #endif + #include + #include + #include + #include + #include + #include + +AR2HandleT *ar2CreateHandleMod( ARParamLT *cparamLT, AR_PIXEL_FORMAT pixFormat/*, int threadNum*/ ) +{ + AR2HandleT *ar2Handle; + + ar2Handle = ar2CreateHandleSubMod( pixFormat, cparamLT->param.xsize, cparamLT->param.ysize/*, threadNum*/ ); + + ar2Handle->trackingMode = AR2_TRACKING_6DOF; + ar2Handle->cparamLT = cparamLT; + ar2Handle->icpHandle = icpCreateHandle( cparamLT->param.mat ); + icpSetInlierProbability( ar2Handle->icpHandle, 0.0 ); + + return ar2Handle; +} + +AR2HandleT *ar2CreateHandleSubMod( int pixFormat, int xsize, int ysize/*, int threadNum*/ ) +{ + AR2HandleT *ar2Handle; + int i; + + arMalloc(ar2Handle, AR2HandleT, 1); + ar2Handle->pixFormat = pixFormat; + ar2Handle->xsize = xsize; + ar2Handle->ysize = ysize; +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE + ar2Handle->blurMethod = AR2_DEFAULT_BLUR_METHOD; + ar2Handle->blurLevel = AR2_DEFAULT_BLUR_LEVEL; +#endif + ar2Handle->searchSize = AR2_DEFAULT_SEARCH_SIZE; + ar2Handle->templateSize1 = AR2_DEFAULT_TS1; + ar2Handle->templateSize2 = AR2_DEFAULT_TS2; + ar2Handle->searchFeatureNum = AR2_DEFAULT_SEARCH_FEATURE_NUM; + if( ar2Handle->searchFeatureNum > AR2_SEARCH_FEATURE_MAX ) { + ar2Handle->searchFeatureNum = AR2_SEARCH_FEATURE_MAX; + } + ar2Handle->simThresh = AR2_DEFAULT_SIM_THRESH; + ar2Handle->trackingThresh = AR2_DEFAULT_TRACKING_THRESH; + + + ar2Handle->threadNum = 1; + + for( i = 0; i < ar2Handle->threadNum; i++ ) { + arMalloc( ar2Handle->arg[i].mfImage, ARUint8, xsize*ysize ); + ar2Handle->arg[i].templ = NULL; +// ar2Handle->threadHandle[i] = threadInit(i, &(ar2Handle->arg[i]), ar2Tracking2d); + } + + return ar2Handle; +} + + static float ar2GetTransMat ( ICPHandleT *icpHandle, float initConv[3][4], + float pos2d[][2], float pos3d[][3], int num, float conv[3][4], int robustMode ); + static float ar2GetTransMatHomography ( float initConv[3][4], float pos2d[][2], float pos3d[][3], int num, + float conv[3][4], int robustMode, float inlierProb ); + static float ar2GetTransMatHomography2 ( float initConv[3][4], float pos2d[][2], float pos3d[][3], int num, float conv[3][4] ); + static float ar2GetTransMatHomographyRobust ( float initConv[3][4], float pos2d[][2], float pos3d[][3], int num, float conv[3][4], float inlierProb ); + static int extractVisibleFeatures ( const ARParamLT *cparamLT, const float trans1[][3][4], AR2SurfaceSetT *surfaceSet, + AR2TemplateCandidateT candidate[], + AR2TemplateCandidateT candidate2[] ); + static int extractVisibleFeaturesHomography( int xsize, int ysize, float trans1[][3][4], AR2SurfaceSetT *surfaceSet, + AR2TemplateCandidateT candidate[], + AR2TemplateCandidateT candidate2[] ); + static int getDeltaS( float H[8], float dU[], float J_U_H[][8], int n ); + + int ar2DeleteHandleMod( AR2HandleT **ar2Handle ) + { + int i; + + if( *ar2Handle == NULL ) return -1; + + (*ar2Handle)->threadNum = 1; // Force single thread for now, since we aren't using threads in this modified version. + + for( i = 0; i < (*ar2Handle)->threadNum; i++ ) { + //threadWaitQuit( (*ar2Handle)->threadHandle[i] ); + //threadFree( &((*ar2Handle)->threadHandle[i]) ); + if( (*ar2Handle)->arg[i].mfImage != NULL ) free( (*ar2Handle)->arg[i].mfImage ); + if( (*ar2Handle)->arg[i].templ != NULL ) ar2FreeTemplate( (*ar2Handle)->arg[i].templ ); + #if AR2_CAPABLE_ADAPTIVE_TEMPLATE + if( (*ar2Handle)->arg[i].templ2 != NULL ) ar2FreeTemplate ( (*ar2Handle)->arg[i].templ2 ); + #endif + } + + if( (*ar2Handle)->icpHandle != NULL ) icpDeleteHandle( &((*ar2Handle)->icpHandle) ); + //if( (*ar2Handle)->cparamLT != NULL ) arParamLTFree( (*ar2Handle)->cparamLT ); + free( *ar2Handle ); + *ar2Handle = NULL; + + return 0; + } + + int ar2TrackingMod( AR2HandleT *ar2Handle, AR2SurfaceSetT *surfaceSet, ARUint8 *dataPtr, float trans[3][4], float *err ) + { + AR2TemplateCandidateT *candidatePtr; + AR2TemplateCandidateT *cp[AR2_THREAD_MAX]; + #if AR2_CAPABLE_ADAPTIVE_TEMPLATE + float aveBlur; + #endif + int num, num2; + int i, j, k; + + if (!ar2Handle || !surfaceSet || !dataPtr || !trans || !err) return (-1); + + if( surfaceSet->contNum <= 0 ) { + ARLOGd("ar2Tracking() error: ar2SetInitTrans() must be called first.\n"); + return -2; + } + + *err = 0.0F; + + for( i = 0; i < surfaceSet->num; i++ ) { + arUtilMatMulf( (const float (*)[4])surfaceSet->trans1, (const float (*)[4])surfaceSet->surface[i].trans, ar2Handle->wtrans1[i] ); + if( surfaceSet->contNum > 1 ) arUtilMatMulf( (const float (*)[4])surfaceSet->trans2, (const float (*)[4])surfaceSet->surface[i].trans, ar2Handle->wtrans2[i] ); + if( surfaceSet->contNum > 2 ) arUtilMatMulf( (const float (*)[4])surfaceSet->trans3, (const float (*)[4])surfaceSet->surface[i].trans, ar2Handle->wtrans3[i] ); + } + + if( ar2Handle->trackingMode == AR2_TRACKING_6DOF ) { + extractVisibleFeatures(ar2Handle->cparamLT, ar2Handle->wtrans1, surfaceSet, ar2Handle->candidate, ar2Handle->candidate2); + } + else { + extractVisibleFeaturesHomography(ar2Handle->xsize, ar2Handle->ysize, ar2Handle->wtrans1, surfaceSet, ar2Handle->candidate, ar2Handle->candidate2); + } + + candidatePtr = ar2Handle->candidate; + #if AR2_CAPABLE_ADAPTIVE_TEMPLATE + aveBlur = 0.0F; + #endif + i = 0; // Counts up to searchFeatureNum. + num = 0; + while( i < ar2Handle->searchFeatureNum ) { + num2 = num; + for( j = 0; j < ar2Handle->threadNum; j++ ) { + if( i == ar2Handle->searchFeatureNum ) break; + + k = ar2SelectTemplate( candidatePtr, surfaceSet->prevFeature, num2, ar2Handle->pos, ar2Handle->xsize, ar2Handle->ysize ); + if( k < 0 ) { + if( candidatePtr == ar2Handle->candidate ) { + candidatePtr = ar2Handle->candidate2; + k = ar2SelectTemplate( candidatePtr, surfaceSet->prevFeature, num2, ar2Handle->pos, ar2Handle->xsize, ar2Handle->ysize ); + if( k < 0 ) break; // PRL 2012-05-15: Give up if we can't select template from alternate candidate either. + } + else break; + } + + cp[j] = &(candidatePtr[k]); + ar2Handle->pos[num2][0] = candidatePtr[k].sx; + ar2Handle->pos[num2][1] = candidatePtr[k].sy; + ar2Handle->arg[j].ar2Handle = ar2Handle; + ar2Handle->arg[j].surfaceSet = surfaceSet; + ar2Handle->arg[j].candidate = &(candidatePtr[k]); + ar2Handle->arg[j].dataPtr = dataPtr; + + // threadStartSignal( ar2Handle->threadHandle[j] ); + num2++; + if( num2 == 5 ) num2 = num; + i++; + } + k = j; + if( k == 0 ) break; + + for( j = 0; j < k; j++ ) { + { + AR2Tracking2DParamT* arg = &ar2Handle->arg[j]; + arg->ret = ar2Tracking2dSub(arg->ar2Handle, arg->surfaceSet, arg->candidate, + arg->dataPtr, arg->mfImage, &(arg->templ), &(arg->result)); + } + //threadEndWait( ar2Handle->threadHandle[j] ); + + if( ar2Handle->arg[j].ret == 0 && ar2Handle->arg[j].result.sim > ar2Handle->simThresh ) { + if( ar2Handle->trackingMode == AR2_TRACKING_6DOF ) { + #ifdef ARDOUBLE_IS_FLOAT + arParamObserv2Ideal(ar2Handle->cparamLT->param.dist_factor, + ar2Handle->arg[j].result.pos2d[0], ar2Handle->arg[j].result.pos2d[1], + &ar2Handle->pos2d[num][0], &ar2Handle->pos2d[num][1], ar2Handle->cparamLT->param.dist_function_version); + #else + ARdouble pos2d0, pos2d1; + arParamObserv2Ideal(ar2Handle->cparamLT->param.dist_factor, + (ARdouble)(ar2Handle->arg[j].result.pos2d[0]), (ARdouble)(ar2Handle->arg[j].result.pos2d[1]), + &pos2d0, &pos2d1, ar2Handle->cparamLT->param.dist_function_version); + ar2Handle->pos2d[num][0] = (float)pos2d0; + ar2Handle->pos2d[num][1] = (float)pos2d1; + #endif + } + else { + ar2Handle->pos2d[num][0] = ar2Handle->arg[j].result.pos2d[0]; + ar2Handle->pos2d[num][1] = ar2Handle->arg[j].result.pos2d[1]; + } + ar2Handle->pos3d[num][0] = ar2Handle->arg[j].result.pos3d[0]; + ar2Handle->pos3d[num][1] = ar2Handle->arg[j].result.pos3d[1]; + ar2Handle->pos3d[num][2] = ar2Handle->arg[j].result.pos3d[2]; + ar2Handle->pos[num][0] = cp[j]->sx; + ar2Handle->pos[num][1] = cp[j]->sy; + ar2Handle->usedFeature[num].snum = cp[j]->snum; + ar2Handle->usedFeature[num].level = cp[j]->level; + ar2Handle->usedFeature[num].num = cp[j]->num; + ar2Handle->usedFeature[num].flag = 0; + #if AR2_CAPABLE_ADAPTIVE_TEMPLATE + aveBlur += ar2Handle->arg[j].result.blurLevel; + #endif + num++; + } + } + } + for( i = 0; i < num; i++ ) { + surfaceSet->prevFeature[i] = ar2Handle->usedFeature[i]; + } + surfaceSet->prevFeature[num].flag = -1; + //ARLOG("------\nNum = %d\n", num); + + if( ar2Handle->trackingMode == AR2_TRACKING_6DOF ) { + if( num < 3 ) { + surfaceSet->contNum = 0; + return -3; + } + *err = ar2GetTransMat( ar2Handle->icpHandle, surfaceSet->trans1, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 0 ); + //ARLOG("outlier 0%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + icpSetInlierProbability( ar2Handle->icpHandle, 0.8F ); + *err = ar2GetTransMat( ar2Handle->icpHandle, trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1 ); + //ARLOG("outlier 20%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + icpSetInlierProbability( ar2Handle->icpHandle, 0.6F ); + *err = ar2GetTransMat( ar2Handle->icpHandle, trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1 ); + //ARLOG("outlier 60%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + icpSetInlierProbability( ar2Handle->icpHandle, 0.4F ); + *err = ar2GetTransMat( ar2Handle->icpHandle, trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1 ); + //ARLOG("outlier 60%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + icpSetInlierProbability( ar2Handle->icpHandle, 0.0F ); + *err = ar2GetTransMat( ar2Handle->icpHandle, trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1 ); + //ARLOG("outlier Max: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + surfaceSet->contNum = 0; + #if AR2_CAPABLE_ADAPTIVE_TEMPLATE + if( ar2Handle->blurMethod == AR2_ADAPTIVE_BLUR ) ar2Handle->blurLevel = AR2_DEFAULT_BLUR_LEVEL; // Reset the blurLevel. + #endif + return -4; + } + } + } + } + } + } + else { + if( num < 3 ) { + surfaceSet->contNum = 0; + return -3; + } + *err = ar2GetTransMatHomography( surfaceSet->trans1, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 0, 1.0F ); + //ARLOG("outlier 0%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + *err = ar2GetTransMatHomography( trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1, 0.8F ); + //ARLOG("outlier 20%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + *err = ar2GetTransMatHomography( trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1, 0.6F ); + //ARLOG("outlier 40%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + *err = ar2GetTransMatHomography( trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1, 0.4F ); + //ARLOG("outlier 60%%: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + *err = ar2GetTransMatHomography( trans, ar2Handle->pos2d, ar2Handle->pos3d, num, trans, 1, 0.0F ); + //ARLOG("outlier Max: err = %f, num = %d\n", *err, num); + if( *err > ar2Handle->trackingThresh ) { + surfaceSet->contNum = 0; + #if AR2_CAPABLE_ADAPTIVE_TEMPLATE + if( ar2Handle->blurMethod == AR2_ADAPTIVE_BLUR ) ar2Handle->blurLevel = AR2_DEFAULT_BLUR_LEVEL; // Reset the blurLevel. + #endif + return -4; + } + } + } + } + } + } + + #if AR2_CAPABLE_ADAPTIVE_TEMPLATE + if( ar2Handle->blurMethod == AR2_ADAPTIVE_BLUR ) { + aveBlur = aveBlur/num + 0.5F; + ar2Handle->blurLevel += (int)aveBlur - 1; + if( ar2Handle->blurLevel < 1 ) ar2Handle->blurLevel = 1; + if( ar2Handle->blurLevel >= AR2_BLUR_IMAGE_MAX-1 ) ar2Handle->blurLevel = AR2_BLUR_IMAGE_MAX-2; + } + #endif + + surfaceSet->contNum++; + for( j = 0; j < 3; j++ ) { + for( i = 0; i < 4; i++ ) surfaceSet->trans3[j][i] = surfaceSet->trans2[j][i]; + } + for( j = 0; j < 3; j++ ) { + for( i = 0; i < 4; i++ ) surfaceSet->trans2[j][i] = surfaceSet->trans1[j][i]; + } + for( j = 0; j < 3; j++ ) { + for( i = 0; i < 4; i++ ) surfaceSet->trans1[j][i] = trans[j][i]; + } + + return 0; + } + + static int extractVisibleFeatures(const ARParamLT *cparamLT, const float trans1[][3][4], AR2SurfaceSetT *surfaceSet, + AR2TemplateCandidateT candidate[], // candidates inside DPI range of [mindpi, maxdpi]. + AR2TemplateCandidateT candidate2[]) // candidates inside DPI range of [mindpi/2, maxdpi*2]. + { + float trans2[3][4]; + float sx, sy; + float wpos[2], w[2]; + float vdir[3], vlen; + int xsize, ysize; + int i, j, k, l, l2; + + xsize = cparamLT->param.xsize; + ysize = cparamLT->param.ysize; + + l = l2 = 0; + for( i = 0; i < surfaceSet->num; i++ ) { + for(j=0;j<3;j++) for(k=0;k<4;k++) trans2[j][k] = trans1[i][j][k]; + + for( j = 0; j < surfaceSet->surface[i].featureSet->num; j++ ) { + for( k = 0; k < surfaceSet->surface[i].featureSet->list[j].num; k++ ) { + + if( ar2MarkerCoord2ScreenCoord2( cparamLT, (const float (*)[4])trans2, + surfaceSet->surface[i].featureSet->list[j].coord[k].mx, + surfaceSet->surface[i].featureSet->list[j].coord[k].my, + &sx, &sy) < 0 ) continue; + if( sx < 0 || sx >= xsize ) continue; + if( sy < 0 || sy >= ysize ) continue; + + vdir[0] = trans2[0][0] * surfaceSet->surface[i].featureSet->list[j].coord[k].mx + + trans2[0][1] * surfaceSet->surface[i].featureSet->list[j].coord[k].my + + trans2[0][3]; + vdir[1] = trans2[1][0] * surfaceSet->surface[i].featureSet->list[j].coord[k].mx + + trans2[1][1] * surfaceSet->surface[i].featureSet->list[j].coord[k].my + + trans2[1][3]; + vdir[2] = trans2[2][0] * surfaceSet->surface[i].featureSet->list[j].coord[k].mx + + trans2[2][1] * surfaceSet->surface[i].featureSet->list[j].coord[k].my + + trans2[2][3]; + vlen = sqrtf( vdir[0]*vdir[0] + vdir[1]*vdir[1] + vdir[2]*vdir[2] ); + vdir[0] /= vlen; + vdir[1] /= vlen; + vdir[2] /= vlen; + if( vdir[0]*trans2[0][2] + vdir[1]*trans2[1][2] + vdir[2]*trans2[2][2] > -0.1f ) continue; + + wpos[0] = surfaceSet->surface[i].featureSet->list[j].coord[k].mx; + wpos[1] = surfaceSet->surface[i].featureSet->list[j].coord[k].my; + ar2GetResolution( cparamLT, (const float (*)[4])trans2, wpos, w ); + //if( w[0] <= surfaceSet->surface[i].featureSet->list[j].maxdpi + // && w[0] >= surfaceSet->surface[i].featureSet->list[j].mindpi ) { + if( w[1] <= surfaceSet->surface[i].featureSet->list[j].maxdpi + && w[1] >= surfaceSet->surface[i].featureSet->list[j].mindpi ) { + if( l == AR2_TRACKING_CANDIDATE_MAX ) { + ARLOGe("### Feature candidates for tracking are overflow.\n"); + candidate[l].flag = -1; + return -1; + } + candidate[l].snum = i; + candidate[l].level = j; + candidate[l].num = k; + candidate[l].sx = sx; + candidate[l].sy = sy; + candidate[l].flag = 0; + l++; + } + else if( w[1] <= surfaceSet->surface[i].featureSet->list[j].maxdpi*2 + && w[1] >= surfaceSet->surface[i].featureSet->list[j].mindpi/2 ) { + if( l2 == AR2_TRACKING_CANDIDATE_MAX ) { + candidate2[l2].flag = -1; + } + else { + candidate2[l2].snum = i; + candidate2[l2].level = j; + candidate2[l2].num = k; + candidate2[l2].sx = sx; + candidate2[l2].sy = sy; + candidate2[l2].flag = 0; + l2++; + } + } + } + } + } + candidate[l].flag = -1; + candidate2[l2].flag = -1; + + return 0; + } + + static int extractVisibleFeaturesHomography(int xsize, int ysize, float trans1[][3][4], AR2SurfaceSetT *surfaceSet, + AR2TemplateCandidateT candidate[], + AR2TemplateCandidateT candidate2[]) + { + float trans2[3][4]; + float sx, sy; + float wpos[2], w[2]; + //float vdir[3], vlen; + int i, j, k, l, l2; + + l = l2 = 0; + for( i = 0; i < surfaceSet->num; i++ ) { + for(j=0;j<3;j++) for(k=0;k<4;k++) trans2[j][k] = trans1[i][j][k]; + + for( j = 0; j < surfaceSet->surface[i].featureSet->num; j++ ) { + for( k = 0; k < surfaceSet->surface[i].featureSet->list[j].num; k++ ) { + + if( ar2MarkerCoord2ScreenCoord2( NULL, (const float (*)[4])trans2, + surfaceSet->surface[i].featureSet->list[j].coord[k].mx, + surfaceSet->surface[i].featureSet->list[j].coord[k].my, + &sx, &sy) < 0 ) continue; + if( sx < 0 || sx >= xsize ) continue; + if( sy < 0 || sy >= ysize ) continue; + + /* + vdir[0] = trans2[0][0] * surfaceSet->surface[i].featureSet->list[j].coord[k].mx + + trans2[0][1] * surfaceSet->surface[i].featureSet->list[j].coord[k].my + + trans2[0][3]; + vdir[1] = trans2[1][0] * surfaceSet->surface[i].featureSet->list[j].coord[k].mx + + trans2[1][1] * surfaceSet->surface[i].featureSet->list[j].coord[k].my + + trans2[1][3]; + vdir[2] = trans2[2][0] * surfaceSet->surface[i].featureSet->list[j].coord[k].mx + + trans2[2][1] * surfaceSet->surface[i].featureSet->list[j].coord[k].my + + trans2[2][3]; + vlen = sqrtf( vdir[0]*vdir[0] + vdir[1]*vdir[1] + vdir[2]*vdir[2] ); + vdir[0] /= vlen; + vdir[1] /= vlen; + vdir[2] /= vlen; + if( vdir[0]*trans2[0][2] + vdir[1]*trans2[1][2] + vdir[2]*trans2[2][2] > -0.1 ) continue; + */ + + wpos[0] = surfaceSet->surface[i].featureSet->list[j].coord[k].mx; + wpos[1] = surfaceSet->surface[i].featureSet->list[j].coord[k].my; + ar2GetResolution( NULL, (const float (*)[4])trans2, wpos, w ); + //if( w[0] <= surfaceSet->surface[i].featureSet->list[j].maxdpi + // && w[0] >= surfaceSet->surface[i].featureSet->list[j].mindpi ) { + if( w[1] <= surfaceSet->surface[i].featureSet->list[j].maxdpi + && w[1] >= surfaceSet->surface[i].featureSet->list[j].mindpi ) { + if( l == AR2_TRACKING_CANDIDATE_MAX ) { + ARLOGe("### Feature candidates for tracking are overflow.\n"); + candidate[l].flag = -1; + return -1; + } + candidate[l].snum = i; + candidate[l].level = j; + candidate[l].num = k; + candidate[l].sx = sx; + candidate[l].sy = sy; + candidate[l].flag = 0; + l++; + } + else if( w[1] <= surfaceSet->surface[i].featureSet->list[j].maxdpi*2 + && w[1] >= surfaceSet->surface[i].featureSet->list[j].mindpi/2 ) { + if( l2 == AR2_TRACKING_CANDIDATE_MAX ) { + candidate2[l2].flag = -1; + } + else { + candidate2[l2].snum = i; + candidate2[l2].level = j; + candidate2[l2].num = k; + candidate2[l2].sx = sx; + candidate2[l2].sy = sy; + candidate2[l2].flag = 0; + l2++; + } + } + } + } + } + candidate[l].flag = -1; + candidate2[l2].flag = -1; + + return 0; + } + + static float ar2GetTransMat( ICPHandleT *icpHandle, float initConv[3][4], float pos2d[][2], float pos3d[][3], int num, + float conv[3][4], int robustMode ) + { + ICPDataT data; + float dx, dy, dz; + ARdouble initMat[3][4], mat[3][4]; + ARdouble err; + int i, j; + + arMalloc( data.screenCoord, ICP2DCoordT, num ); + arMalloc( data.worldCoord, ICP3DCoordT, num ); + + dx = dy = dz = 0.0; + for( i = 0; i < num; i++ ) { + dx += pos3d[i][0]; + dy += pos3d[i][1]; + dz += pos3d[i][2]; + } + dx /= num; + dy /= num; + dz /= num; + + for( i = 0; i < num; i++ ) { + data.screenCoord[i].x = pos2d[i][0]; + data.screenCoord[i].y = pos2d[i][1]; + data.worldCoord[i].x = pos3d[i][0] - dx; + data.worldCoord[i].y = pos3d[i][1] - dy; + data.worldCoord[i].z = pos3d[i][2] - dz; + } + data.num = num; + + for( j = 0; j < 3; j++ ) { + for( i = 0; i < 3; i++ ) initMat[j][i] = (ARdouble)(initConv[j][i]); + } + initMat[0][3] = (ARdouble)(initConv[0][0] * dx + initConv[0][1] * dy + initConv[0][2] * dz + initConv[0][3]); + initMat[1][3] = (ARdouble)(initConv[1][0] * dx + initConv[1][1] * dy + initConv[1][2] * dz + initConv[1][3]); + initMat[2][3] = (ARdouble)(initConv[2][0] * dx + initConv[2][1] * dy + initConv[2][2] * dz + initConv[2][3]); + + if( robustMode == 0 ) { + if( icpPoint( icpHandle, &data, initMat, mat, &err ) < 0 ) { + err = 100000000.0F; + } + } + else { + if( icpPointRobust( icpHandle, &data, initMat, mat, &err ) < 0 ) { + err = 100000000.0F; + } + } + + free( data.screenCoord ); + free( data.worldCoord ); + + for( j = 0; j < 3; j++ ) { + for( i = 0; i < 3; i++ ) conv[j][i] = (float)mat[j][i]; + } + conv[0][3] = (float)(mat[0][3] - mat[0][0] * dx - mat[0][1] * dy - mat[0][2] * dz); + conv[1][3] = (float)(mat[1][3] - mat[1][0] * dx - mat[1][1] * dy - mat[1][2] * dz); + conv[2][3] = (float)(mat[2][3] - mat[2][0] * dx - mat[2][1] * dy - mat[2][2] * dz); + + return (float)err; + } + + static float ar2GetTransMatHomography( float initConv[3][4], float pos2d[][2], float pos3d[][3], int num, + float conv[3][4], int robustMode, float inlierProb ) + { + if( robustMode == 0 ) { + return ar2GetTransMatHomography2( initConv, pos2d, pos3d, num, conv ); + } + else { + return ar2GetTransMatHomographyRobust( initConv, pos2d, pos3d, num, conv, inlierProb ); + } + } + + static float ar2GetTransMatHomography2( float initConv[3][4], float pos2d[][2], float pos3d[][3], int num, float conv[3][4] ) + { + float err = 100000000.0F; + float *J_U_H; + float *dU; + float hx, hy, h, hh, ux, uy, dx, dy; + float dH[8]; + float err0, err1; + int i, j; + + if( num < 4 ) return err; + if( initConv[2][3] == 0.0F ) return err; + + if( (J_U_H = (float *)malloc( sizeof(float)*16*num )) == NULL ) { + ARLOGe("Error: malloc\n"); + return -1; + } + if( (dU = (float *)malloc( sizeof(float)*2*num )) == NULL ) { + ARLOGe("Error: malloc\n"); + free(J_U_H); + return -1; + } + for( j = 0; j < 3; j++ ) { + for( i = 0; i < 4; i++ ) conv[j][i] = initConv[j][i]/ initConv[2][3]; + } + + for( i = 0;; i++ ) { + err1 = 0.0F; + for( j = 0; j < num; j++ ) { + hx = conv[0][0] * pos3d[j][0] + conv[0][1] * pos3d[j][1] + conv[0][3]; + hy = conv[1][0] * pos3d[j][0] + conv[1][1] * pos3d[j][1] + conv[1][3]; + h = conv[2][0] * pos3d[j][0] + conv[2][1] * pos3d[j][1] + 1.0f; + if( h == 0.0 ) { + free(J_U_H); + free(dU); + return err; + } + hh = h*h; + ux = hx / h; + uy = hy / h; + dx = pos2d[j][0] - ux; + dy = pos2d[j][1] - uy; + err1 += dx*dx + dy*dy; + dU[j*2+0] = dx; + dU[j*2+1] = dy; + J_U_H[16*j+ 0] = pos3d[j][0]/h; + J_U_H[16*j+ 1] = pos3d[j][1]/h; + J_U_H[16*j+ 2] = 1.0f/h; + J_U_H[16*j+ 3] = 0.0f; + J_U_H[16*j+ 4] = 0.0f; + J_U_H[16*j+ 5] = 0.0f; + J_U_H[16*j+ 6] = -pos3d[j][0]*hx/hh; + J_U_H[16*j+ 7] = -pos3d[j][1]*hx/hh; + J_U_H[16*j+ 8] = 0.0f; + J_U_H[16*j+ 9] = 0.0f; + J_U_H[16*j+10] = 0.0f; + J_U_H[16*j+11] = pos3d[j][0]/h; + J_U_H[16*j+12] = pos3d[j][1]/h; + J_U_H[16*j+13] = 1.0f/h; + J_U_H[16*j+14] = -pos3d[j][0]*hy/hh; + J_U_H[16*j+15] = -pos3d[j][1]*hy/hh; + } + err1 /= num; + //ARLOG("Loop[%d]: err = %15.10f\n", i, err1); + if( err1 < ICP_BREAK_LOOP_ERROR_THRESH ) break; + if( i > 0 && err1 < ICP_BREAK_LOOP_ERROR_THRESH2 && err1/err0 > ICP_BREAK_LOOP_ERROR_RATIO_THRESH ) break; + if( i == ICP_MAX_LOOP ) break; + err0 = err1; + + if( getDeltaS( dH, dU, (float (*)[8])J_U_H, num*2 ) < 0 ) { + free(J_U_H); + free(dU); + return err; + } + //for(j=0;j<8;j++) ARLOG("%f\t", dH[j]); ARLOG("\n"); + conv[0][0] += dH[0]; + conv[0][1] += dH[1]; + conv[0][3] += dH[2]; + conv[1][0] += dH[3]; + conv[1][1] += dH[4]; + conv[1][3] += dH[5]; + conv[2][0] += dH[6]; + conv[2][1] += dH[7]; + } + + //ARLOG("*********** %f\n", err1); + //ARLOG("Loop = %d\n", i); + + free(J_U_H); + free(dU); + + return err1; + } + + #define K2_FACTOR 4.0F + + static int compE( const void *a, const void *b ) + { + float c; + c = *(float *)a - *(float *)b; + if( c < 0.0F ) return -1; + if( c > 0.0F ) return 1; + return 0; + } + + static float ar2GetTransMatHomographyRobust ( float initConv[3][4], float pos2d[][2], float pos3d[][3], int num, float conv[3][4], float inlierProb ) + { + float err = 100000000.0F; + float *J_U_H; + float *dU; + float *E, *E2, K2, W; + float hx, hy, h, hh, ux, uy, dx, dy; + float dH[8]; + float err0, err1; + int inlierNum; + int i, j, k; + + if( num < 4 ) return err; + if( initConv[2][3] == 0.0F ) return err; + + inlierNum = (int)(num * inlierProb) - 1; + if( inlierNum < 4 ) inlierNum = 4; + + if( (J_U_H = (float *)malloc( sizeof(float)*16*num )) == NULL ) { + ARLOGe("Error: malloc\n"); + return -1; + } + if( (dU = (float *)malloc( sizeof(float)*2*num )) == NULL ) { + ARLOGe("Error: malloc\n"); + free(J_U_H); + return -1; + } + if( (E = (float *)malloc( sizeof(float)*num )) == NULL ) { + ARLOGe("Error: malloc\n"); + free(J_U_H); + free(dU); + return -1; + } + if( (E2 = (float *)malloc( sizeof(float)*num )) == NULL ) { + ARLOGe("Error: malloc\n"); + free(J_U_H); + free(dU); + free(E); + return -1; + } + + for( j = 0; j < 3; j++ ) { + for( i = 0; i < 4; i++ ) conv[j][i] = initConv[j][i]/ initConv[2][3]; + } + + for( i = 0;; i++ ) { + for( j = 0; j < num; j++ ) { + hx = conv[0][0] * pos3d[j][0] + conv[0][1] * pos3d[j][1] + conv[0][3]; + hy = conv[1][0] * pos3d[j][0] + conv[1][1] * pos3d[j][1] + conv[1][3]; + h = conv[2][0] * pos3d[j][0] + conv[2][1] * pos3d[j][1] + 1.0F; + if( h == 0.0f ) { + free(J_U_H); + free(dU); + free(E); + free(E2); + return err; + } + hh = h*h; + ux = hx / h; + uy = hy / h; + dx = pos2d[j][0] - ux; + dy = pos2d[j][1] - uy; + dU[j*2+0] = dx; + dU[j*2+1] = dy; + E[j] = E2[j] = dx*dx + dy*dy; + + J_U_H[16*j+ 0] = pos3d[j][0]/h; + J_U_H[16*j+ 1] = pos3d[j][1]/h; + J_U_H[16*j+ 2] = 1.0f/h; + J_U_H[16*j+ 3] = 0.0f; + J_U_H[16*j+ 4] = 0.0f; + J_U_H[16*j+ 5] = 0.0f; + J_U_H[16*j+ 6] = -pos3d[j][0]*hx/hh; + J_U_H[16*j+ 7] = -pos3d[j][1]*hx/hh; + J_U_H[16*j+ 8] = 0.0f; + J_U_H[16*j+ 9] = 0.0f; + J_U_H[16*j+10] = 0.0f; + J_U_H[16*j+11] = pos3d[j][0]/h; + J_U_H[16*j+12] = pos3d[j][1]/h; + J_U_H[16*j+13] = 1.0f/h; + J_U_H[16*j+14] = -pos3d[j][0]*hy/hh; + J_U_H[16*j+15] = -pos3d[j][1]*hy/hh; + } + qsort(E2, num, sizeof(float), compE); + K2 = E2[inlierNum] * K2_FACTOR; + if( K2 < 16.0F ) K2 = 16.0F; + + err1 = 0.0F; + for( j = 0; j < num; j++ ) { + if( E2[j] > K2 ) err1 += K2/6.0F; + else err1 += K2/6.0F * (1.0F - (1.0F-E2[j]/K2)*(1.0F-E2[j]/K2)*(1.0F-E2[j]/K2)); + } + err1 /= num; + //ARLOG("Loop[%d]: err = %15.10f\n", i, err1); + if( err1 < ICP_BREAK_LOOP_ERROR_THRESH ) break; + if( i > 0 && err1 < ICP_BREAK_LOOP_ERROR_THRESH2 && err1/err0 > ICP_BREAK_LOOP_ERROR_RATIO_THRESH ) break; + if( i == ICP_MAX_LOOP ) break; + err0 = err1; + + k = 0; + for( j = 0; j < num; j++ ) { + if( E[j] <= K2 ) { + W = (1.0F - E[j]/K2)*(1.0F - E[j]/K2); + J_U_H[k*8+ 0] = W * J_U_H[16*j+0]; + J_U_H[k*8+ 1] = W * J_U_H[16*j+1]; + J_U_H[k*8+ 2] = W * J_U_H[16*j+2]; + J_U_H[k*8+ 3] = W * J_U_H[16*j+3]; + J_U_H[k*8+ 4] = W * J_U_H[16*j+4]; + J_U_H[k*8+ 5] = W * J_U_H[16*j+5]; + J_U_H[k*8+ 6] = W * J_U_H[16*j+6]; + J_U_H[k*8+ 7] = W * J_U_H[16*j+7]; + J_U_H[k*8+ 8] = W * J_U_H[16*j+8]; + J_U_H[k*8+ 9] = W * J_U_H[16*j+9]; + J_U_H[k*8+10] = W * J_U_H[16*j+10]; + J_U_H[k*8+11] = W * J_U_H[16*j+11]; + J_U_H[k*8+12] = W * J_U_H[16*j+12]; + J_U_H[k*8+13] = W * J_U_H[16*j+13]; + J_U_H[k*8+14] = W * J_U_H[16*j+14]; + J_U_H[k*8+15] = W * J_U_H[16*j+15]; + dU[k+0] = W * dU[j*2+0]; + dU[k+1] = W * dU[j*2+1]; + k+=2; + } + } + if( k < 6 ) { + free(J_U_H); + free(dU); + free(E); + free(E2); + return -1; + } + + if( getDeltaS( dH, dU, (float (*)[8])J_U_H, k ) < 0 ) { + free(J_U_H); + free(dU); + free(E); + free(E2); + return err; + } + //for(j=0;j<8;j++) ARLOG("%f\t", dH[j]); ARLOG("\n"); + conv[0][0] += dH[0]; + conv[0][1] += dH[1]; + conv[0][3] += dH[2]; + conv[1][0] += dH[3]; + conv[1][1] += dH[4]; + conv[1][3] += dH[5]; + conv[2][0] += dH[6]; + conv[2][1] += dH[7]; + } + + //ARLOG("*********** %f\n", err1); + //ARLOG("Loop = %d\n", i); + + free(J_U_H); + free(dU); + free(E); + free(E2); + + return err1; + } + + static int getDeltaS( float H[8], float dU[], float J_U_H[][8], int n ) + { + ARMatf matH, matU, matJ; + ARMatf *matJt, *matJtJ, *matJtU; + int ret = 0; + + matH.row = 8; + matH.clm = 1; + matH.m = H; + + matU.row = n; + matU.clm = 1; + matU.m = dU; + + matJ.row = n; + matJ.clm = 8; + matJ.m = &J_U_H[0][0]; + + matJt = arMatrixAllocTransf( &matJ ); + if( matJt == NULL ) { + ret = -1; + goto bail; + } + matJtJ = arMatrixAllocMulf( matJt, &matJ ); + if( matJtJ == NULL ) { + ret = -1; + goto bail1; + } + matJtU = arMatrixAllocMulf( matJt, &matU ); + if( matJtU == NULL ) { + ret = -1; + goto bail2; + } + if( arMatrixSelfInvf(matJtJ) < 0 ) { + ret = -1; + goto bail3; + } + + arMatrixMulf( &matH, matJtJ, matJtU ); + bail3: + arMatrixFreef( matJtU ); + bail2: + arMatrixFreef( matJtJ ); + bail1: + arMatrixFreef( matJt ); + bail: + return (ret); + } \ No newline at end of file diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c new file mode 100644 index 0000000..9dacef3 --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c @@ -0,0 +1,216 @@ +/* + * AR2/tracking2d.c + * ARToolKit5 + * + * This file is part of ARToolKit. + * + * ARToolKit is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARToolKit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ARToolKit. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2015 Daqri, LLC. + * Copyright 2006-2015 ARToolworks, Inc. + * + * Author(s): Hirokazu Kato, Philip Lamb + * Modified version by @misdake + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE +int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, + ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, + AR2Template2T **templ2, AR2Tracking2DResultT *result ); +#else +int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, + ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, + AR2Tracking2DResultT *result ); +#endif + +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE +int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, + ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, + AR2Template2T **templ2, AR2Tracking2DResultT *result ) +#else +int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, + ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, + AR2Tracking2DResultT *result ) +#endif +{ +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE + AR2Template2T *templ2; +#endif + int snum, level, fnum; + int search[3][2]; + int bx, by; + + snum = candidate->snum; + level = candidate->level; + fnum = candidate->num; + + if( *templ == NULL ) *templ = ar2GenTemplate( handle->templateSize1, handle->templateSize2 ); +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE + if( *templ2 == NULL ) *templ2 = ar2GenTemplate2( handle->templateSize1, handle->templateSize2 ); +#endif + +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE + if( handle->blurMethod == AR2_CONSTANT_BLUR ) { + if( ar2SetTemplateSub( handle->cparamLT, + (const float (*)[4])handle->wtrans1[snum], + surfaceSet->surface[snum].imageSet, + &(surfaceSet->surface[snum].featureSet->list[level]), + fnum, + handle->blurLevel, + *templ ) < 0 ) return -1; + + if( (*templ)->vlen * (*templ)->vlen + < ((*templ)->xts1+(*templ)->xts2+1) * ((*templ)->yts1+(*templ)->yts2+1) + * AR2_DEFAULT_TRACKING_SD_THRESH * AR2_DEFAULT_TRACKING_SD_THRESH ) { + return -1; + } + } + else { + if( ar2SetTemplate2Sub( handle->cparamLT, + (const float (*)[4])handle->wtrans1[snum], + surfaceSet->surface[snum].imageSet, + &(surfaceSet->surface[snum].featureSet->list[level]), + fnum, + handle->blurLevel, + *templ2 ) < 0 ) return -1; + + if( (*templ2)->vlen[1] * (*templ2)->vlen[1] + < ((*templ2)->xts1+(*templ2)->xts2+1) * ((*templ2)->yts1+(*templ2)->yts2+1) + * AR2_DEFAULT_TRACKING_SD_THRESH * AR2_DEFAULT_TRACKING_SD_THRESH ) { + return -1; + } + } +#else + if( ar2SetTemplateSub( handle->cparamLT, + (const float (*)[4])handle->wtrans1[snum], + surfaceSet->surface[snum].imageSet, + &(surfaceSet->surface[snum].featureSet->list[level]), + fnum, + *templ ) < 0 ) return -1; + + if( (*templ)->vlen * (*templ)->vlen + < ((*templ)->xts1 + (*templ)->xts2 + 1) * ((*templ)->yts1 + (*templ)->yts2 + 1) + * AR2_DEFAULT_TRACKING_SD_THRESH * AR2_DEFAULT_TRACKING_SD_THRESH ) { + return -1; + } +#endif + + // Get the screen coordinates for up to three previous positions of this feature into search[][]. + if( surfaceSet->contNum == 1 ) { + ar2GetSearchPoint( handle->cparamLT, + (const float (*)[4])handle->wtrans1[snum], NULL, NULL, + &(surfaceSet->surface[snum].featureSet->list[level].coord[fnum]), + search ); + } + else if( surfaceSet->contNum == 2 ) { + ar2GetSearchPoint( handle->cparamLT, + (const float (*)[4])handle->wtrans1[snum], + (const float (*)[4])handle->wtrans2[snum], NULL, + &(surfaceSet->surface[snum].featureSet->list[level].coord[fnum]), + search ); + } + else { + ar2GetSearchPoint( handle->cparamLT, + (const float (*)[4])handle->wtrans1[snum], + (const float (*)[4])handle->wtrans2[snum], + (const float (*)[4])handle->wtrans3[snum], + &(surfaceSet->surface[snum].featureSet->list[level].coord[fnum]), + search ); + } + +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE + if( handle->blurMethod == AR2_CONSTANT_BLUR ) { + if( ar2GetBestMatching( dataPtr, + mfImage, + handle->xsize, + handle->ysize, + handle->pixFormat, + *templ, + handle->searchSize, + handle->searchSize, + search, + &bx, &by, + &(result->sim)) < 0 ) { + return -1; + } + result->blurLevel = handle->blurLevel; + } + else { + if( ar2GetBestMatching2( dataPtr, + mfImage, + handle->xsize, + handle->ysize, + handle->pixFormat, + *templ2, + handle->searchSize, + handle->searchSize, + search, + &bx, &by, + &(result->sim), + &(result->blurLevel)) < 0 ) { + return -1; + } + } +#else + if( ar2GetBestMatching( dataPtr, + mfImage, + handle->xsize, + handle->ysize, + handle->pixFormat, + *templ, + handle->searchSize, + handle->searchSize, + search, + &bx, &by, + &(result->sim)) < 0 ) { + return -1; + } +#endif + + result->pos2d[0] = (float)bx; + result->pos2d[1] = (float)by; + result->pos3d[0] = surfaceSet->surface[snum].trans[0][0] * surfaceSet->surface[snum].featureSet->list[level].coord[fnum].mx + + surfaceSet->surface[snum].trans[0][1] * surfaceSet->surface[snum].featureSet->list[level].coord[fnum].my + + surfaceSet->surface[snum].trans[0][3]; + result->pos3d[1] = surfaceSet->surface[snum].trans[1][0] * surfaceSet->surface[snum].featureSet->list[level].coord[fnum].mx + + surfaceSet->surface[snum].trans[1][1] * surfaceSet->surface[snum].featureSet->list[level].coord[fnum].my + + surfaceSet->surface[snum].trans[1][3]; + result->pos3d[2] = surfaceSet->surface[snum].trans[2][0] * surfaceSet->surface[snum].featureSet->list[level].coord[fnum].mx + + surfaceSet->surface[snum].trans[2][1] * surfaceSet->surface[snum].featureSet->list[level].coord[fnum].my + + surfaceSet->surface[snum].trans[2][3]; + + return 0; +} diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c new file mode 100644 index 0000000..b0868ae --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c @@ -0,0 +1,230 @@ +/* + * trackingSub.c + * ARToolKit5 + * + * Disclaimer: IMPORTANT: This Daqri software is supplied to you by Daqri + * LLC ("Daqri") in consideration of your agreement to the following + * terms, and your use, installation, modification or redistribution of + * this Daqri software constitutes acceptance of these terms. If you do + * not agree with these terms, please do not use, install, modify or + * redistribute this Daqri software. + * + * In consideration of your agreement to abide by the following terms, and + * subject to these terms, Daqri grants you a personal, non-exclusive + * license, under Daqri's copyrights in this original Daqri software (the + * "Daqri Software"), to use, reproduce, modify and redistribute the Daqri + * Software, with or without modifications, in source and/or binary forms; + * provided that if you redistribute the Daqri Software in its entirety and + * without modifications, you must retain this notice and the following + * text and disclaimers in all such redistributions of the Daqri Software. + * Neither the name, trademarks, service marks or logos of Daqri LLC may + * be used to endorse or promote products derived from the Daqri Software + * without specific prior written permission from Daqri. Except as + * expressly stated in this notice, no other rights or licenses, express or + * implied, are granted by Daqri herein, including but not limited to any + * patent rights that may be infringed by your derivative works or by other + * works in which the Daqri Software may be incorporated. + * + * The Daqri Software is provided by Daqri on an "AS IS" basis. DAQRI + * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + * THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE, REGARDING THE DAQRI SOFTWARE OR ITS USE AND + * OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + * + * IN NO EVENT SHALL DAQRI BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + * MODIFICATION AND/OR DISTRIBUTION OF THE DAQRI SOFTWARE, HOWEVER CAUSED + * AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + * STRICT LIABILITY OR OTHERWISE, EVEN IF DAQRI HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 2015 Daqri LLC. All Rights Reserved. + * Copyright 2010-2015 ARToolworks, Inc. All Rights Reserved. + * + * Author(s): Hirokazu Kato, Philip Lamb. + * + */ + +#include + +#include +#include +#include + +typedef struct { + KpmHandle *kpmHandle; // KPM-related data. + ARUint8 *imageLumaPtr; // Pointer to image being tracked. + int imageSize; // Bytes per image. + TrackingInitResult results[TRACKING_INIT_MAX_RESULTS]; // Every matched page. + int resultNum; // How many of results[] are set. +} TrackingInitHandle; + +static void *trackingInitMain( THREAD_HANDLE_T *threadHandle ); + + +int trackingInitQuit( THREAD_HANDLE_T **threadHandle_p ) +{ + TrackingInitHandle *trackingInitHandle; + + if (!threadHandle_p) { + ARLOGe("trackingInitQuit(): Error: NULL threadHandle_p.\n"); + return (-1); + } + if (!*threadHandle_p) return 0; + + threadWaitQuit( *threadHandle_p ); + trackingInitHandle = (TrackingInitHandle *)threadGetArg(*threadHandle_p); + if (trackingInitHandle) { + free( trackingInitHandle->imageLumaPtr ); + free( trackingInitHandle ); + } + threadFree( threadHandle_p ); + return 0; +} + +THREAD_HANDLE_T *trackingInitInit( KpmHandle *kpmHandle ) +{ + TrackingInitHandle *trackingInitHandle; + THREAD_HANDLE_T *threadHandle; + + if (!kpmHandle) { + ARLOGe("trackingInitInit(): Error: NULL KpmHandle.\n"); + return (NULL); + } + + trackingInitHandle = (TrackingInitHandle *)malloc(sizeof(TrackingInitHandle)); + if( trackingInitHandle == NULL ) return NULL; + trackingInitHandle->kpmHandle = kpmHandle; + trackingInitHandle->imageSize = kpmHandleGetXSize(kpmHandle) * kpmHandleGetYSize(kpmHandle); + trackingInitHandle->imageLumaPtr = (ARUint8 *)malloc(trackingInitHandle->imageSize); + trackingInitHandle->resultNum = 0; + + threadHandle = threadInit(0, trackingInitHandle, trackingInitMain); + return threadHandle; +} + +int trackingInitStart( THREAD_HANDLE_T *threadHandle, ARUint8 *imageLumaPtr ) +{ + TrackingInitHandle *trackingInitHandle; + + if (!threadHandle || !imageLumaPtr) { + ARLOGe("trackingInitStart(): Error: NULL threadHandle or imagePtr.\n"); + return (-1); + } + + trackingInitHandle = (TrackingInitHandle *)threadGetArg(threadHandle); + if (!trackingInitHandle) { + ARLOGe("trackingInitStart(): Error: NULL trackingInitHandle.\n"); + return (-1); + } + memcpy( trackingInitHandle->imageLumaPtr, imageLumaPtr, trackingInitHandle->imageSize ); + threadStartSignal( threadHandle ); + + return 0; +} + +int trackingInitGetResults( THREAD_HANDLE_T *threadHandle, TrackingInitResult results[], int maxResults, int *resultNum ) +{ + TrackingInitHandle *trackingInitHandle; + int n; + + if (!threadHandle || !results || !resultNum || maxResults <= 0) { + ARLOGe("trackingInitGetResults(): Error: NULL argument or maxResults <= 0.\n"); + return (-1); + } + if( threadGetStatus( threadHandle ) == 0 ) return 0; + threadEndWait( threadHandle ); + trackingInitHandle = (TrackingInitHandle *)threadGetArg(threadHandle); + if (!trackingInitHandle) return (-1); + + n = trackingInitHandle->resultNum < maxResults ? trackingInitHandle->resultNum : maxResults; + memcpy(results, trackingInitHandle->results, n * sizeof(TrackingInitResult)); + *resultNum = n; + return 1; +} + +int trackingInitGetResult( THREAD_HANDLE_T *threadHandle, float trans[3][4], int *page ) +{ + TrackingInitResult results[TRACKING_INIT_MAX_RESULTS]; + int resultNum = 0; + int best, ret, i, j; + + if (!threadHandle || !trans || !page) { + ARLOGe("trackingInitGetResult(): Error: NULL threadHandle or trans or page.\n"); + return (-1); + } + + ret = trackingInitGetResults( threadHandle, results, TRACKING_INIT_MAX_RESULTS, &resultNum ); + if (ret != 1) return ret; // 0 still running, -1 error. + if (resultNum <= 0) return (-1); // Finished, no page matched. + + // The best match, as the single-result KPM chose it: the most inliers, + // then the lowest error. + best = 0; + for (i = 1; i < resultNum; i++) { + if (results[i].inlierNum > results[best].inlierNum || + (results[i].inlierNum == results[best].inlierNum && + results[i].error < results[best].error)) { + best = i; + } + } + for (j = 0; j < 3; j++) for (i = 0; i < 4; i++) trans[j][i] = results[best].trans[j][i]; + *page = results[best].page; + return 1; +} + +static void *trackingInitMain( THREAD_HANDLE_T *threadHandle ) +{ + TrackingInitHandle *trackingInitHandle; + KpmHandle *kpmHandle; + KpmResult *kpmResult = NULL; + int kpmResultNum; + ARUint8 *imageLumaPtr; + int i, j, k; + + if (!threadHandle) { + ARLOGe("Error starting tracking thread: empty THREAD_HANDLE_T.\n"); + return (NULL); + } + trackingInitHandle = (TrackingInitHandle *)threadGetArg(threadHandle); + if (!threadHandle) { + ARLOGe("Error starting tracking thread: empty trackingInitHandle.\n"); + return (NULL); + } + kpmHandle = trackingInitHandle->kpmHandle; + imageLumaPtr = trackingInitHandle->imageLumaPtr; + if (!kpmHandle || !imageLumaPtr) { + ARLOGe("Error starting tracking thread: empty kpmHandle/imageLumaPtr.\n"); + return (NULL); + } + ARLOGi("Start tracking thread.\n"); + + for(;;) { + if( threadStartWait(threadHandle) < 0 ) break; + + kpmMatching(kpmHandle, imageLumaPtr); + // Fetch the result array on every pass: kpmSetRefDataSet() reallocates + // it when markers are loaded, so a pointer cached earlier can dangle. + kpmGetResult( kpmHandle, &kpmResult, &kpmResultNum ); + trackingInitHandle->resultNum = 0; + for( i = 0; i < kpmResultNum; i++ ) { + if( kpmResult[i].camPoseF != 0 ) continue; + if( trackingInitHandle->resultNum >= TRACKING_INIT_MAX_RESULTS ) { + ARLOGe("trackingInitMain(): more than %d pages matched; ignoring the rest.\n", TRACKING_INIT_MAX_RESULTS); + break; + } + TrackingInitResult *result = &trackingInitHandle->results[trackingInitHandle->resultNum++]; + result->page = kpmResult[i].pageNo; + result->error = kpmResult[i].error; + result->inlierNum = kpmResult[i].inlierNum; + for (j = 0; j < 3; j++) for (k = 0; k < 4; k++) result->trans[j][k] = kpmResult[i].camPose[j][k]; + } + + threadEndSignal(threadHandle); + } + + ARLOGi("End tracking thread.\n"); + return (NULL); +} From 10560440be43c57d93d56f46c1c3c4bceb0dbf4f Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 00:17:14 +0200 Subject: [PATCH 02/11] fix(nft): make WebARKitNFT helpers build outside emscripten (#75) - rename ar2Tracking2dSub to ar2Tracking2dSubMod: it is public here but static in AR2/tracking2d.c - markerDecompress.c: drop unused , use - markerDecompress.h: add include guard Co-Authored-By: Claude Opus 5.5 --- .../WebARKitTrackers/WebARKitNFT/markerDecompress.h | 5 +++++ .../include/WebARKitTrackers/WebARKitNFT/trackingMod.h | 2 +- WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c | 3 +-- WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c | 2 +- WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c | 8 ++++---- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h index 25bf987..4216d79 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h @@ -1,3 +1,6 @@ +#ifndef MARKER_DECOMPRESS_H +#define MARKER_DECOMPRESS_H + #include #ifdef __cplusplus @@ -19,3 +22,5 @@ void extractDataAndSave(const char* str, const char* name); #ifdef __cplusplus } #endif + +#endif // MARKER_DECOMPRESS_H diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h index 28a9d15..0ab3904 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h @@ -64,7 +64,7 @@ extern "C" { #endif -int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, +int ar2Tracking2dSubMod ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, AR2Tracking2DResultT *result ); diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c index 6312467..69e4b62 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c @@ -1,5 +1,4 @@ #include -#include #ifdef _WIN32 # include @@ -12,7 +11,7 @@ #include #include #include -#include +#include const int mem_size_4mb = 4*1024*1024; diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c index bbb7aec..67d7636 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c @@ -211,7 +211,7 @@ AR2HandleT *ar2CreateHandleSubMod( int pixFormat, int xsize, int ysize/*, int th for( j = 0; j < k; j++ ) { { AR2Tracking2DParamT* arg = &ar2Handle->arg[j]; - arg->ret = ar2Tracking2dSub(arg->ar2Handle, arg->surfaceSet, arg->candidate, + arg->ret = ar2Tracking2dSubMod(arg->ar2Handle, arg->surfaceSet, arg->candidate, arg->dataPtr, arg->mfImage, &(arg->templ), &(arg->result)); } //threadEndWait( ar2Handle->threadHandle[j] ); diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c index 9dacef3..c30f8f9 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c @@ -47,21 +47,21 @@ #include #if AR2_CAPABLE_ADAPTIVE_TEMPLATE -int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, +int ar2Tracking2dSubMod ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, AR2Template2T **templ2, AR2Tracking2DResultT *result ); #else -int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, +int ar2Tracking2dSubMod ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, AR2Tracking2DResultT *result ); #endif #if AR2_CAPABLE_ADAPTIVE_TEMPLATE -int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, +int ar2Tracking2dSubMod ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, AR2Template2T **templ2, AR2Tracking2DResultT *result ) #else -int ar2Tracking2dSub ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, +int ar2Tracking2dSubMod ( AR2HandleT *handle, AR2SurfaceSetT *surfaceSet, AR2TemplateCandidateT *candidate, ARUint8 *dataPtr, ARUint8 *mfImage, AR2TemplateT **templ, AR2Tracking2DResultT *result ) #endif From 98b0de108104db2b8e55faafa84b13ca253fbf23 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 12:24:12 +0200 Subject: [PATCH 03/11] build(nft): add WEBARKIT_BUILD_NFT CMake option and WebARKitNFT target (#75) - WEBARKIT_BUILD_OPTICAL (ON): the existing OpenCV WebARKitLib target - WEBARKIT_BUILD_NFT (OFF): static WebARKitNFT library with the NFT helpers plus the AR, ARICP, AR2, KPM and ARUtil sources they need (lists mirror jsartoolkitNFT/tools/makem.js); no OpenCV - WEBARKIT_NFT_THREADS (OFF): adds trackingSub and ARUtil/thread_sub Emscripten uses the libjpeg/zlib ports; native builds use find_package. Built with emsdk 4.0.17; linked and smoke-tested with threads on. Co-Authored-By: Claude Opus 5.5 --- WebARKit/CMakeLists.txt | 11 +++ .../WebARKitNFT/CMakeLists.txt | 86 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt diff --git a/WebARKit/CMakeLists.txt b/WebARKit/CMakeLists.txt index 1e6ea94..2fe3f69 100644 --- a/WebARKit/CMakeLists.txt +++ b/WebARKit/CMakeLists.txt @@ -8,6 +8,17 @@ if(VERSION GREATER 3.24) cmake_policy(SET CMP0135 NEW) endif() +option(WEBARKIT_BUILD_OPTICAL "Build the OpenCV optical tracker (WebARKitLib target)" ON) +option(WEBARKIT_BUILD_NFT "Build the NFT tracking helpers (WebARKitNFT target, no OpenCV)" OFF) + +if(WEBARKIT_BUILD_NFT) + add_subdirectory(WebARKitTrackers/WebARKitNFT) +endif() + +if(NOT WEBARKIT_BUILD_OPTICAL) + return() +endif() + include(FetchContent) include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/OpenCVEm.cmake) diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt b/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt new file mode 100644 index 0000000..52db595 --- /dev/null +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt @@ -0,0 +1,86 @@ +# WebARKitNFT: NFT tracking helpers (trackingMod, markerDecompress, trackingSub) +# plus the ARToolKit sources they need (AR, ARICP, AR2, KPM, ARUtil). +# The source lists mirror jsartoolkitNFT/tools/makem.js. No OpenCV dependency. + +option(WEBARKIT_NFT_THREADS "Build the threaded KPM worker (trackingSub) with pthreads" OFF) + +get_filename_component(WEBARKITLIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../.. ABSOLUTE) +set(ARTK_SRC ${WEBARKITLIB_ROOT}/lib/SRC) + +file(GLOB AR_SOURCES + ${ARTK_SRC}/AR/*.c + ${ARTK_SRC}/AR/arLabelingSub/*.c + ${ARTK_SRC}/ARICP/*.c +) + +set(ARUTIL_SOURCES + ${ARTK_SRC}/ARUtil/log.c + ${ARTK_SRC}/ARUtil/file_utils.c +) + +set(AR2_SOURCES + handle.c imageSet.c jpeg.c marker.c featureMap.c featureSet.c + selectTemplate.c surface.c tracking.c tracking2d.c matching.c matching2.c + template.c searchPoint.c coord.c util.c +) +list(TRANSFORM AR2_SOURCES PREPEND ${ARTK_SRC}/AR2/) + +set(KPM_SOURCES + kpmHandle.cpp kpmRefDataSet.cpp kpmMatching.cpp kpmResult.cpp kpmUtil.cpp kpmFopen.c + FreakMatcher/detectors/DoG_scale_invariant_detector.cpp + FreakMatcher/detectors/gaussian_scale_space_pyramid.cpp + FreakMatcher/detectors/gradients.cpp + FreakMatcher/detectors/orientation_assignment.cpp + FreakMatcher/detectors/pyramid.cpp + FreakMatcher/facade/visual_database_facade.cpp + FreakMatcher/matchers/hough_similarity_voting.cpp + FreakMatcher/matchers/freak.cpp + FreakMatcher/framework/date_time.cpp + FreakMatcher/framework/image.cpp + FreakMatcher/framework/logger.cpp + FreakMatcher/framework/timers.cpp +) +list(TRANSFORM KPM_SOURCES PREPEND ${ARTK_SRC}/KPM/) + +set(NFT_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/trackingMod.c + ${CMAKE_CURRENT_SOURCE_DIR}/trackingMod2d.c + ${CMAKE_CURRENT_SOURCE_DIR}/markerDecompress.c +) + +if(WEBARKIT_NFT_THREADS) + list(APPEND ARUTIL_SOURCES ${ARTK_SRC}/ARUtil/thread_sub.c) + list(APPEND NFT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/trackingSub.c) +endif() + +add_library(WebARKitNFT STATIC + ${AR_SOURCES} ${ARUTIL_SOURCES} ${AR2_SOURCES} ${KPM_SOURCES} ${NFT_SOURCES} +) + +target_include_directories(WebARKitNFT + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${WEBARKITLIB_ROOT}/include + PRIVATE + ${ARTK_SRC}/KPM/FreakMatcher +) + +target_compile_definitions(WebARKitNFT PUBLIC HAVE_NFT) + +if(EMSCRIPTEN) + # libjpeg and zlib come from the Emscripten ports. + target_compile_options(WebARKitNFT PUBLIC -sUSE_LIBJPEG=1 -sUSE_ZLIB=1) + target_link_options(WebARKitNFT PUBLIC -sUSE_LIBJPEG=1 -sUSE_ZLIB=1) + if(WEBARKIT_NFT_THREADS) + target_compile_options(WebARKitNFT PUBLIC -pthread) + target_link_options(WebARKitNFT PUBLIC -pthread) + endif() +else() + find_package(JPEG REQUIRED) + find_package(ZLIB REQUIRED) + target_link_libraries(WebARKitNFT PUBLIC JPEG::JPEG ZLIB::ZLIB) + if(WEBARKIT_NFT_THREADS) + find_package(Threads REQUIRED) + target_link_libraries(WebARKitNFT PUBLIC Threads::Threads) + endif() +endif() From 67feabbd1d1cab0a4cc6884fdbdf7f01450c9b73 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 13:34:20 +0200 Subject: [PATCH 04/11] fix(nft): return -1 from markerDecompress on errors instead of exit() decompressMarkers() and extractDataAndSave() killed the whole process on a missing or malformed .zft. They now free their buffers and return -1; extractDataAndSave() returns int. Callers in jsartoolkitNFT already ignore the result. Co-Authored-By: Claude Opus 5.5 --- .../WebARKitNFT/markerDecompress.h | 2 +- .../WebARKitNFT/markerDecompress.c | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h index 4216d79..3ba663c 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h @@ -17,7 +17,7 @@ typedef struct char* nameConcat(const char *s1, const char *s2); FILE *openZFT( const char *filename, const char *ext); int decompressMarkers(const char* src, const char* outTemp); -void extractDataAndSave(const char* str, const char* name); +int extractDataAndSave(const char* str, const char* name); #ifdef __cplusplus } diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c index 69e4b62..7751cee 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c @@ -28,7 +28,8 @@ int decompressMarkers(const char* src, const char* outTemp){ if ( fp == NULL ) { ARLOGe("Error opening .zft file\n"); - exit(EXIT_FAILURE); + free(c); + return -1; } fseek (fp, 0, SEEK_END); @@ -40,7 +41,9 @@ int decompressMarkers(const char* src, const char* outTemp){ if (in == NULL) { ARLOGe("Error mallocing %i bytes for inflate\n", filesize); - exit(EXIT_FAILURE); + fclose(fp); + free(c); + return -1; } ret = fread (in, 1, filesize, fp); fclose (fp); @@ -63,14 +66,14 @@ int decompressMarkers(const char* src, const char* outTemp){ free(in); - extractDataAndSave(c, outTemp); + int result = extractDataAndSave(c, outTemp); free(c); - return 0; + return result; // return markerData; } -void extractDataAndSave(const char* str, const char* name){ +int extractDataAndSave(const char* str, const char* name){ // string and variable name structure // // iset_final_index @@ -97,7 +100,7 @@ void extractDataAndSave(const char* str, const char* name){ char *fsetInitialIndex = strstr(str, "\",\"fset\":\""); if (fsetInitialIndex == NULL) { ARLOGe("Error: 'fset' not found in the string.\n"); - exit(EXIT_FAILURE); + return -1; } int fset_initial_index = (fsetInitialIndex - str); @@ -108,7 +111,7 @@ void extractDataAndSave(const char* str, const char* name){ char *fset3InitialIndex = strstr(str, "\",\"fset3\":\""); if (fset3InitialIndex == NULL) { ARLOGe("Error: 'fset3' not found in the string.\n"); - exit(EXIT_FAILURE); + return -1; } int fset3_initial_index = (fset3InitialIndex - str); int fset3_final_index = (fset3_initial_index + 11); @@ -118,7 +121,7 @@ void extractDataAndSave(const char* str, const char* name){ char *endOfStr = strstr(str, "\"}"); if (endOfStr == NULL) { ARLOGe("Error: end of string not found.\n"); - exit(EXIT_FAILURE); + return -1; } int endPos = endOfStr - str; @@ -127,7 +130,7 @@ void extractDataAndSave(const char* str, const char* name){ // ---ISET--- if (iset_content_size <= 0) { ARLOGe("Error: Invalid iset_content_size: %d\n", iset_content_size); - exit(EXIT_FAILURE); + return -1; } char *iset_contentHex = malloc(iset_content_size); strncpy(iset_contentHex, str + iset_final_index, iset_content_size); @@ -165,7 +168,7 @@ void extractDataAndSave(const char* str, const char* name){ free(fset3Name); free(fset3_contentHex); - // return tempMarkerData; + return 0; } FILE *openZFT( const char *filename, const char *ext) From d913c0263e844b0fe8e8507626e2750c421b64d6 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 13:34:21 +0200 Subject: [PATCH 05/11] fix(nft): initialise icpHandle and cparamLT in ar2CreateHandleSubMod The function is public, but left icpHandle uninitialised, so ar2DeleteHandleMod() freed a garbage pointer on a handle made with it directly (segfault on native builds; wasm memory starts zeroed, so it went unnoticed). Co-Authored-By: Claude Opus 5.5 --- WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c index 67d7636..bd30c1d 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod.c @@ -86,6 +86,8 @@ AR2HandleT *ar2CreateHandleSubMod( int pixFormat, int xsize, int ysize/*, int th } ar2Handle->simThresh = AR2_DEFAULT_SIM_THRESH; ar2Handle->trackingThresh = AR2_DEFAULT_TRACKING_THRESH; + ar2Handle->cparamLT = NULL; + ar2Handle->icpHandle = NULL; ar2Handle->threadNum = 1; @@ -93,6 +95,9 @@ AR2HandleT *ar2CreateHandleSubMod( int pixFormat, int xsize, int ysize/*, int th for( i = 0; i < ar2Handle->threadNum; i++ ) { arMalloc( ar2Handle->arg[i].mfImage, ARUint8, xsize*ysize ); ar2Handle->arg[i].templ = NULL; +#if AR2_CAPABLE_ADAPTIVE_TEMPLATE + ar2Handle->arg[i].templ2 = NULL; +#endif // ar2Handle->threadHandle[i] = threadInit(i, &(ar2Handle->arg[i]), ar2Tracking2d); } From 95f3621b2cbe3e5daf44563ed472e7a42cbba7f8 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 13:34:21 +0200 Subject: [PATCH 06/11] fix(kpm): add missing and includes FreakMatcher relied on libc++ pulling them in transitively; GCC 13 (libstdc++) fails without them. Co-Authored-By: Claude Opus 5.5 --- lib/SRC/KPM/FreakMatcher/facade/visual_database_facade.cpp | 1 + lib/SRC/KPM/FreakMatcher/math/hamming.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/SRC/KPM/FreakMatcher/facade/visual_database_facade.cpp b/lib/SRC/KPM/FreakMatcher/facade/visual_database_facade.cpp index c943083..5fc5472 100644 --- a/lib/SRC/KPM/FreakMatcher/facade/visual_database_facade.cpp +++ b/lib/SRC/KPM/FreakMatcher/facade/visual_database_facade.cpp @@ -34,6 +34,7 @@ // #include "visual_database_facade.h" +#include #include #include #include diff --git a/lib/SRC/KPM/FreakMatcher/math/hamming.h b/lib/SRC/KPM/FreakMatcher/math/hamming.h index 06b8383..6c75881 100644 --- a/lib/SRC/KPM/FreakMatcher/math/hamming.h +++ b/lib/SRC/KPM/FreakMatcher/math/hamming.h @@ -35,6 +35,8 @@ #pragma once +#include + namespace vision { /** From 8e2280675bd461fa755ebd75971b2a0049315f9c Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 13:34:22 +0200 Subject: [PATCH 07/11] test(nft): add WebARKitNFT gtest suite and run it in CI (#75) - webarkit_nft_test: markerDecompress (round trip, missing file, malformed data), NFTMarkerState defaults, ar2 handle create/delete, trackingSub worker start/quit - WebARKitNFT also builds the minizip sources file_utils.c needs (crypt, ioapi, unzip, zip), with USE_FILE32API on Emscripten - CI installs zlib1g-dev and runs webarkit_nft_test Co-Authored-By: Claude Opus 5.5 --- .github/workflows/test.yml | 6 +- .../WebARKitNFT/CMakeLists.txt | 10 +++ tests/CMakeLists.txt | 10 ++- tests/webarkit_nft_test.cc | 88 +++++++++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 tests/webarkit_nft_test.cc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac03f7d..41a4038 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,12 +10,12 @@ jobs: uses: actions/setup-node@v6 with: node-version: '22.x' - - name: Update Ubuntu and install libjpeg-dev + - name: Update Ubuntu and install libjpeg-dev and zlib1g-dev run: | - sudo apt-get update && sudo apt install libjpeg-dev + sudo apt-get update && sudo apt install libjpeg-dev zlib1g-dev - name: Build and test WebARKitLib run: | - cd tests && mkdir build && cd build && cmake -DEMSCRIPTEN_COMP=0 .. && make && ./webarkit_test + cd tests && mkdir build && cd build && cmake -DEMSCRIPTEN_COMP=0 .. && make && ./webarkit_test && ./webarkit_nft_test - name: Build WebARKitLib with Emscripten (Docker) run: | cd .. diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt b/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt index 52db595..3f1b104 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt @@ -16,6 +16,11 @@ file(GLOB AR_SOURCES set(ARUTIL_SOURCES ${ARTK_SRC}/ARUtil/log.c ${ARTK_SRC}/ARUtil/file_utils.c + # minizip, used by file_utils.c (zip/unzip helpers) + ${ARTK_SRC}/ARUtil/crypt.c + ${ARTK_SRC}/ARUtil/ioapi.c + ${ARTK_SRC}/ARUtil/unzip.c + ${ARTK_SRC}/ARUtil/zip.c ) set(AR2_SOURCES @@ -68,6 +73,11 @@ target_include_directories(WebARKitNFT target_compile_definitions(WebARKitNFT PUBLIC HAVE_NFT) if(EMSCRIPTEN) + # Emscripten has no fopen64/ftello64/fseeko64: let minizip use the plain calls. + set_source_files_properties( + ${ARTK_SRC}/ARUtil/ioapi.c ${ARTK_SRC}/ARUtil/unzip.c ${ARTK_SRC}/ARUtil/zip.c + PROPERTIES COMPILE_DEFINITIONS USE_FILE32API + ) # libjpeg and zlib come from the Emscripten ports. target_compile_options(WebARKitNFT PUBLIC -sUSE_LIBJPEG=1 -sUSE_ZLIB=1) target_link_options(WebARKitNFT PUBLIC -sUSE_LIBJPEG=1 -sUSE_ZLIB=1) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f6e4369..f52648e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,8 +28,15 @@ FetchContent_MakeAvailable(googletest build_opencv) enable_testing() +set(WEBARKIT_BUILD_NFT ON CACHE BOOL "" FORCE) +set(WEBARKIT_NFT_THREADS ON CACHE BOOL "" FORCE) + add_subdirectory(../WebARKit ../WebARKit/build) +add_executable(webarkit_nft_test webarkit_nft_test.cc) +target_compile_definitions(webarkit_nft_test PRIVATE WEBARKIT_NFT_THREADS) +target_link_libraries(webarkit_nft_test WebARKitNFT GTest::gtest_main) + add_executable( webarkit_test webarkit_test.cc @@ -77,4 +84,5 @@ target_link_libraries( ) include(GoogleTest) -gtest_discover_tests(webarkit_test) \ No newline at end of file +gtest_discover_tests(webarkit_test) +gtest_discover_tests(webarkit_nft_test) diff --git a/tests/webarkit_nft_test.cc b/tests/webarkit_nft_test.cc new file mode 100644 index 0000000..4578c05 --- /dev/null +++ b/tests/webarkit_nft_test.cc @@ -0,0 +1,88 @@ +#include + +#include +#include +#include +#ifdef WEBARKIT_NFT_THREADS +#include +#endif + +#include + +#include +#include +#include +#include + +namespace { + +// A .zft is a zlib-compressed JSON-like string: {"iset":"...","fset":"...","fset3":"..."} +void writeZft(const std::string &basename, const std::string &content) { + uLongf compressedSize = compressBound(content.size()); + std::string compressed(compressedSize, '\0'); + ASSERT_EQ(compress(reinterpret_cast(&compressed[0]), &compressedSize, + reinterpret_cast(content.data()), content.size()), + Z_OK); + std::ofstream out(basename + ".zft", std::ios::binary); + out.write(compressed.data(), compressedSize); +} + +std::string readFile(const std::string &path) { + std::ifstream in(path, std::ios::binary); + std::stringstream ss; + ss << in.rdbuf(); + return ss.str(); +} + +} // namespace + +TEST(MarkerDecompressTest, ExtractsIsetFsetAndFset3) { + writeZft("nft_test_marker", "{\"iset\":\"ISETDATA\",\"fset\":\"FSETDATA\",\"fset3\":\"FSET3DATA\"}"); + + EXPECT_EQ(decompressMarkers("nft_test_marker", "nft_test_out"), 0); + EXPECT_EQ(readFile("nft_test_out.iset"), "ISETDATA"); + EXPECT_EQ(readFile("nft_test_out.fset"), "FSETDATA"); + EXPECT_EQ(readFile("nft_test_out.fset3"), "FSET3DATA"); + + std::remove("nft_test_marker.zft"); + std::remove("nft_test_out.iset"); + std::remove("nft_test_out.fset"); + std::remove("nft_test_out.fset3"); +} + +TEST(MarkerDecompressTest, MissingFileReturnsError) { + EXPECT_EQ(decompressMarkers("does_not_exist", "nft_test_out"), -1); +} + +TEST(MarkerDecompressTest, MalformedContentReturnsError) { + writeZft("nft_test_bad", "{\"iset\":\"ISETDATA\"}"); + EXPECT_EQ(decompressMarkers("nft_test_bad", "nft_test_out"), -1); + std::remove("nft_test_bad.zft"); +} + +TEST(NFTMarkerStateTest, DefaultsToNotTracking) { + NFTMarkerState state; + EXPECT_FALSE(state.tracking); + EXPECT_FLOAT_EQ(state.err, -1.0f); + EXPECT_EQ(state.ftmi, nullptr); + EXPECT_TRUE(state.filterNeedsReset); +} + +TEST(TrackingModTest, CreatesAndDeletesHandle) { + AR2HandleT *handle = ar2CreateHandleSubMod(AR_PIXEL_FORMAT_MONO, 640, 480); + ASSERT_NE(handle, nullptr); + EXPECT_EQ(ar2DeleteHandleMod(&handle), 0); + EXPECT_EQ(handle, nullptr); +} + +#ifdef WEBARKIT_NFT_THREADS +TEST(TrackingSubTest, StartsAndQuitsWorkerThread) { + KpmHandle *kpmHandle = kpmCreateHandle2(640, 480); + ASSERT_NE(kpmHandle, nullptr); + THREAD_HANDLE_T *thread = trackingInitInit(kpmHandle); + ASSERT_NE(thread, nullptr); + EXPECT_EQ(trackingInitQuit(&thread), 0); + EXPECT_EQ(thread, nullptr); + kpmDeleteHandle(&kpmHandle); +} +#endif From 32366bcf29698aa2685affb29dac3b3a3898b2c2 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 13:50:37 +0200 Subject: [PATCH 08/11] docs: document the WebARKitNFT helpers and CMake options (#75) Co-Authored-By: Claude Opus 5.5 --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42448ab..6e43474 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,33 @@ fetched automatically via CMake `FetchContent`. The "planned" part above is webarkit-testing switching its WASM build over to this CMake config (away from `tools/makem.js`). The same config also drives the unit tests (see below). +### NFT helpers (`WebARKit/WebARKitTrackers/WebARKitNFT`) + +The web-adapted NFT helpers formerly in jsartoolkitNFT live here (#75): + +| Header (``) | Provides | +|---|---| +| `trackingMod.h` | `ar2TrackingMod()`, `ar2CreateHandleMod()`, `ar2DeleteHandleMod()` — single-threaded AR2 tracking | +| `markerDecompress.h` | `decompressMarkers()` — unpacks a `.zft` into `.iset`/`.fset`/`.fset3` (returns `-1` on error) | +| `trackingSub.h` | `trackingInit*()` — KPM detection on a worker thread (pthreads) | +| `NFTMarkerState.h` | per-marker tracking state (C++ only) | + +They are built by the `WebARKitNFT` static library, which also compiles the +ARToolKit5 sources it needs (AR, ARICP, AR2, KPM, ARUtil) and does **not** need +OpenCV. CMake options in `WebARKit/CMakeLists.txt`: + +- `WEBARKIT_BUILD_OPTICAL` (ON) — the OpenCV `WebARKitLib` target +- `WEBARKIT_BUILD_NFT` (OFF) — the `WebARKitNFT` target (needs libjpeg and zlib) +- `WEBARKIT_NFT_THREADS` (OFF) — adds `trackingSub` (pthreads) + +```bash +emcmake cmake -S WebARKit -B build-nft -DWEBARKIT_BUILD_OPTICAL=OFF -DWEBARKIT_BUILD_NFT=ON -DWEBARKIT_NFT_THREADS=ON +cmake --build build-nft +``` + ## Tests -C++ unit tests (GoogleTest) live in [`tests/`](tests/) (`webarkit_test.cc`, +C++ unit tests (GoogleTest) live in [`tests/`](tests/) (`webarkit_test.cc`, `webarkit_nft_test.cc`, `CMakeLists.txt`, `pinball.jpg`) and run in CI via [`.github/workflows/test.yml`](https://github.com/webarkit/WebARKitLib/actions/workflows/test.yml). Build them standalone with CMake: From 99a26898a576a70141243fbb97f847791ae886a2 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 14:46:45 +0200 Subject: [PATCH 09/11] fix(nft): always build ARUtil thread helpers in WebARKitNFT AR2 handle.c and tracking.c call threadInit()/threadStartSignal() whatever WEBARKIT_NFT_THREADS says, so thread_sub.c is now always compiled and native builds always link Threads. The option now only adds trackingSub (and -pthread on Emscripten). Reported by Qodo on #76. Co-Authored-By: Claude Opus 5.5 --- WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt b/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt index 3f1b104..e796d10 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/CMakeLists.txt @@ -2,7 +2,7 @@ # plus the ARToolKit sources they need (AR, ARICP, AR2, KPM, ARUtil). # The source lists mirror jsartoolkitNFT/tools/makem.js. No OpenCV dependency. -option(WEBARKIT_NFT_THREADS "Build the threaded KPM worker (trackingSub) with pthreads" OFF) +option(WEBARKIT_NFT_THREADS "Build the threaded KPM worker (trackingSub); on Emscripten also enables -pthread" OFF) get_filename_component(WEBARKITLIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../.. ABSOLUTE) set(ARTK_SRC ${WEBARKITLIB_ROOT}/lib/SRC) @@ -16,6 +16,8 @@ file(GLOB AR_SOURCES set(ARUTIL_SOURCES ${ARTK_SRC}/ARUtil/log.c ${ARTK_SRC}/ARUtil/file_utils.c + # thread helpers: AR2 handle.c/tracking.c call them even without the KPM worker + ${ARTK_SRC}/ARUtil/thread_sub.c # minizip, used by file_utils.c (zip/unzip helpers) ${ARTK_SRC}/ARUtil/crypt.c ${ARTK_SRC}/ARUtil/ioapi.c @@ -54,7 +56,6 @@ set(NFT_SOURCES ) if(WEBARKIT_NFT_THREADS) - list(APPEND ARUTIL_SOURCES ${ARTK_SRC}/ARUtil/thread_sub.c) list(APPEND NFT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/trackingSub.c) endif() @@ -88,9 +89,6 @@ if(EMSCRIPTEN) else() find_package(JPEG REQUIRED) find_package(ZLIB REQUIRED) - target_link_libraries(WebARKitNFT PUBLIC JPEG::JPEG ZLIB::ZLIB) - if(WEBARKIT_NFT_THREADS) - find_package(Threads REQUIRED) - target_link_libraries(WebARKitNFT PUBLIC Threads::Threads) - endif() + find_package(Threads REQUIRED) + target_link_libraries(WebARKitNFT PUBLIC JPEG::JPEG ZLIB::ZLIB Threads::Threads) endif() From fbb89d8290def165a8f158933033dc13a1aa3fd3 Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 14:47:15 +0200 Subject: [PATCH 10/11] fix(nft): harden markerDecompress against malformed and large .zft files - check zlib init/inflate results, grow the output buffer past 4 MB and NUL-terminate it before searching it - validate field order and non-empty lengths before writing anything - write marker files in binary mode, check fopen/fwrite/fclose and remove partial output on failure - drop the remove() of the source archive: it built a wrong name and never deleted anything Reported by Qodo on #76. Co-Authored-By: Claude Opus 5.5 --- .../WebARKitNFT/markerDecompress.c | 248 ++++++++++-------- 1 file changed, 138 insertions(+), 110 deletions(-) diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c index 7751cee..e81cc06 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c @@ -13,161 +13,189 @@ #include #include -const int mem_size_4mb = 4*1024*1024; +static const size_t inflate_chunk = 4*1024*1024; + +/* + * Inflate a whole zlib stream. On success *outLen is the decompressed size and + * the buffer has an extra NUL after it, so it can be searched as a string. + */ +static char *inflateAll(const unsigned char *in, size_t inLen, size_t *outLen) +{ + z_stream strm; + size_t cap = inflate_chunk; + char *out = malloc(cap + 1); + int ret; + + if (out == NULL) return NULL; + memset(&strm, 0, sizeof(strm)); + if (inflateInit(&strm) != Z_OK) { + free(out); + return NULL; + } + strm.next_in = (Bytef *)in; + strm.avail_in = (uInt)inLen; + + do { + if (strm.total_out == cap) { + char *bigger = realloc(out, cap * 2 + 1); + if (bigger == NULL) { + ret = Z_MEM_ERROR; + break; + } + out = bigger; + cap *= 2; + } + strm.next_out = (Bytef *)(out + strm.total_out); + strm.avail_out = (uInt)(cap - strm.total_out); + ret = inflate(&strm, Z_NO_FLUSH); + } while (ret == Z_OK); + + if (ret != Z_STREAM_END) { + ARLOGe("Error inflating .zft data (zlib error %d)\n", ret); + inflateEnd(&strm); + free(out); + return NULL; + } + *outLen = strm.total_out; + out[*outLen] = 0; + inflateEnd(&strm); + return out; +} int decompressMarkers(const char* src, const char* outTemp){ - // markerContentStruct *markerData; FILE *fp; - char* in; - char* out; - int filesize, ret; - const char *ext = "zft"; - char *c = malloc (mem_size_4mb); + unsigned char *in; + char *c; + long filesize; + size_t outLen; - fp = openZFT(src, ext); + fp = openZFT(src, "zft"); if ( fp == NULL ) { ARLOGe("Error opening .zft file\n"); - free(c); return -1; } fseek (fp, 0, SEEK_END); filesize = ftell (fp); fseek (fp, 0, SEEK_SET); + if (filesize <= 0) + { + ARLOGe("Error: empty or unreadable .zft file\n"); + fclose(fp); + return -1; + } in = malloc (filesize); - if (in == NULL) { - ARLOGe("Error mallocing %i bytes for inflate\n", filesize); + ARLOGe("Error mallocing %ld bytes for inflate\n", filesize); + fclose(fp); + return -1; + } + if (fread (in, 1, filesize, fp) != (size_t)filesize) + { + ARLOGe("Error reading .zft file\n"); fclose(fp); - free(c); + free(in); return -1; } - ret = fread (in, 1, filesize, fp); fclose (fp); - char *tempName = nameConcat(src, ext); - remove(tempName); - free(tempName); - - z_stream infstream; - infstream.zalloc = Z_NULL; - infstream.zfree = Z_NULL; - infstream.opaque = Z_NULL; - infstream.avail_in = filesize; - infstream.next_in = (Bytef *) in; - infstream.avail_out = (uInt)mem_size_4mb; - infstream.next_out = (Bytef *) c; - - inflateInit(&infstream); - inflate(&infstream, Z_NO_FLUSH); - inflateEnd(&infstream); + c = inflateAll(in, (size_t)filesize, &outLen); free(in); + if (c == NULL) return -1; int result = extractDataAndSave(c, outTemp); free(c); return result; - // return markerData; } -int extractDataAndSave(const char* str, const char* name){ - // string and variable name structure - // - // iset_final_index - // V - // str = {"iset":"test","fset":"test2","fset3":"test3"} - // ∧ - // Beginning of str or iset_initial_index - // - // - // iset_final_index fset_initial_index - // V V - // str = {"iset":"test","fset":"test2","fset3":"test3"} - // ---- <- iset_content - // - - // markerContentStruct *tempMarkerData; - - FILE *tempIset; - FILE *tempFset; - FILE *tempFset3; - - int iset_final_index = 9; - - char *fsetInitialIndex = strstr(str, "\",\"fset\":\""); - if (fsetInitialIndex == NULL) { - ARLOGe("Error: 'fset' not found in the string.\n"); +/* Write one extracted marker file in binary mode; 0 on success, -1 on error. */ +static int saveMarkerFile(const char *name, const char *ext, const char *data, size_t size) +{ + char *fileName = nameConcat(name, ext); + FILE *fp; + int ok; + + if (fileName == NULL) return -1; + fp = fopen(fileName, "wb"); + if (fp == NULL) { + ARLOGe("Error: cannot create %s\n", fileName); + free(fileName); return -1; } - int fset_initial_index = (fsetInitialIndex - str); + ok = fwrite(data, 1, size, fp) == size; + if (fclose(fp) != 0) ok = 0; + if (!ok) { + ARLOGe("Error: cannot write %s\n", fileName); + remove(fileName); + } + free(fileName); + return ok ? 0 : -1; +} - int fset_final_index = (fset_initial_index + 10); +static void removeMarkerFile(const char *name, const char *ext) +{ + char *fileName = nameConcat(name, ext); + if (fileName == NULL) return; + remove(fileName); + free(fileName); +} - int iset_content_size = fset_initial_index - iset_final_index; +int extractDataAndSave(const char* str, const char* name){ + // The decompressed data is: {"iset":"","fset":"","fset3":""} + static const char isetKey[] = "{\"iset\":\""; + static const char fsetKey[] = "\",\"fset\":\""; + static const char fset3Key[] = "\",\"fset3\":\""; + static const char endKey[] = "\"}"; + + if (strncmp(str, isetKey, sizeof(isetKey) - 1) != 0) { + ARLOGe("Error: 'iset' not found at the start of the string.\n"); + return -1; + } + const char *iset = str + sizeof(isetKey) - 1; - char *fset3InitialIndex = strstr(str, "\",\"fset3\":\""); - if (fset3InitialIndex == NULL) { - ARLOGe("Error: 'fset3' not found in the string.\n"); + const char *fsetKeyPos = strstr(iset, fsetKey); + if (fsetKeyPos == NULL) { + ARLOGe("Error: 'fset' not found in the string.\n"); return -1; } - int fset3_initial_index = (fset3InitialIndex - str); - int fset3_final_index = (fset3_initial_index + 11); + const char *fset = fsetKeyPos + sizeof(fsetKey) - 1; - int fset_content_size = fset3_initial_index - fset_final_index; + const char *fset3KeyPos = strstr(fset, fset3Key); + if (fset3KeyPos == NULL) { + ARLOGe("Error: 'fset3' not found in the string.\n"); + return -1; + } + const char *fset3 = fset3KeyPos + sizeof(fset3Key) - 1; - char *endOfStr = strstr(str, "\"}"); - if (endOfStr == NULL) { + const char *end = strstr(fset3, endKey); + if (end == NULL) { ARLOGe("Error: end of string not found.\n"); return -1; } - int endPos = endOfStr - str; - - int fset3_content_size = endPos - fset3_final_index; - // ---ISET--- - if (iset_content_size <= 0) { - ARLOGe("Error: Invalid iset_content_size: %d\n", iset_content_size); + // Searching each key after the previous one keeps the fields ordered. + size_t isetSize = (size_t)(fsetKeyPos - iset); + size_t fsetSize = (size_t)(fset3KeyPos - fset); + size_t fset3Size = (size_t)(end - fset3); + if (isetSize == 0 || fsetSize == 0 || fset3Size == 0) { + ARLOGe("Error: empty marker field (iset %zu, fset %zu, fset3 %zu bytes).\n", isetSize, fsetSize, fset3Size); return -1; } - char *iset_contentHex = malloc(iset_content_size); - strncpy(iset_contentHex, str + iset_final_index, iset_content_size); - - // tempMarkerData->iset_content = iset_contentHex; - char *isetName = nameConcat(name, ".iset"); - tempIset = fopen(isetName, "w"); - fwrite(iset_contentHex, iset_content_size, 1, tempIset); - // printf(iset_contentHex); - fclose(tempIset); - free(isetName); - free(iset_contentHex); - - // ---FSET--- - char *fset_contentHex = malloc(fset_content_size); - strncpy(fset_contentHex, str + fset_final_index, fset_content_size); - - // tempMarkerData->fset_content = fset_contentHex; - char *fsetName = nameConcat(name, ".fset"); - tempFset = fopen(fsetName, "w"); - fwrite(fset_contentHex, fset_content_size, 1, tempFset); - fclose(tempFset); - free(fsetName); - free(fset_contentHex); - - // ---FSET3--- - char *fset3_contentHex = malloc(fset3_content_size); - strncpy(fset3_contentHex, str + fset3_final_index, fset3_content_size); - - // tempMarkerData->fset3_content = fset3_contentHex; - char *fset3Name = nameConcat(name, ".fset3"); - tempFset3 = fopen(fset3Name, "w"); - fwrite(fset3_contentHex, fset3_content_size, 1, tempFset3); - fclose(tempFset3); - free(fset3Name); - free(fset3_contentHex); + if (saveMarkerFile(name, ".iset", iset, isetSize) != 0) return -1; + if (saveMarkerFile(name, ".fset", fset, fsetSize) != 0) { + removeMarkerFile(name, ".iset"); + return -1; + } + if (saveMarkerFile(name, ".fset3", fset3, fset3Size) != 0) { + removeMarkerFile(name, ".iset"); + removeMarkerFile(name, ".fset"); + return -1; + } return 0; } @@ -196,7 +224,7 @@ char* nameConcat(const char *s1, const char *s2) const size_t len1 = strlen(s1); const size_t len2 = strlen(s2); char *result = malloc(len1 + len2 + 1); // +1 for the null-terminator - // in real code you would check for errors in malloc here + if (result == NULL) return NULL; memcpy(result, s1, len1); memcpy(result + len1, s2, len2 + 1); // +1 to copy the null-terminator return result; From 480fdb9a4f7004a0ec4d75a575aeedeb48748d9d Mon Sep 17 00:00:00 2001 From: kalwalt Date: Tue, 6 Oct 2026 14:47:16 +0200 Subject: [PATCH 11/11] fix(nft): guard trackingSub against overlapping scans and failed init - trackingInitStart() returns -1 while the previous search is uncollected, instead of overwriting the image the worker reads - trackingInitInit() checks the image allocation and thread creation and frees everything on failure - tests: >4 MB marker, binary bytes, field order, non-zlib data, source archive kept, start rejected until results are collected Reported by Qodo on #76. Co-Authored-By: Claude Opus 5.5 --- .../WebARKitNFT/trackingSub.c | 21 +++++ tests/webarkit_nft_test.cc | 88 +++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c index b0868ae..b9bc1d5 100644 --- a/WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c +++ b/WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c @@ -59,6 +59,7 @@ typedef struct { int imageSize; // Bytes per image. TrackingInitResult results[TRACKING_INIT_MAX_RESULTS]; // Every matched page. int resultNum; // How many of results[] are set. + int scanPending; // Client side: a search was started and its results not yet collected. } TrackingInitHandle; static void *trackingInitMain( THREAD_HANDLE_T *threadHandle ); @@ -99,9 +100,21 @@ THREAD_HANDLE_T *trackingInitInit( KpmHandle *kpmHandle ) trackingInitHandle->kpmHandle = kpmHandle; trackingInitHandle->imageSize = kpmHandleGetXSize(kpmHandle) * kpmHandleGetYSize(kpmHandle); trackingInitHandle->imageLumaPtr = (ARUint8 *)malloc(trackingInitHandle->imageSize); + if( trackingInitHandle->imageLumaPtr == NULL ) { + ARLOGe("trackingInitInit(): Error: out of memory for a %d byte image.\n", trackingInitHandle->imageSize); + free( trackingInitHandle ); + return NULL; + } trackingInitHandle->resultNum = 0; + trackingInitHandle->scanPending = 0; threadHandle = threadInit(0, trackingInitHandle, trackingInitMain); + if( threadHandle == NULL ) { + ARLOGe("trackingInitInit(): Error: unable to start the worker thread.\n"); + free( trackingInitHandle->imageLumaPtr ); + free( trackingInitHandle ); + return NULL; + } return threadHandle; } @@ -119,7 +132,14 @@ int trackingInitStart( THREAD_HANDLE_T *threadHandle, ARUint8 *imageLumaPtr ) ARLOGe("trackingInitStart(): Error: NULL trackingInitHandle.\n"); return (-1); } + // The worker reads imageLumaPtr until its results are collected: never + // overwrite it under a running search. + if (trackingInitHandle->scanPending) { + ARLOGe("trackingInitStart(): Error: previous search not collected yet; call trackingInitGetResults() first.\n"); + return (-1); + } memcpy( trackingInitHandle->imageLumaPtr, imageLumaPtr, trackingInitHandle->imageSize ); + trackingInitHandle->scanPending = 1; threadStartSignal( threadHandle ); return 0; @@ -142,6 +162,7 @@ int trackingInitGetResults( THREAD_HANDLE_T *threadHandle, TrackingInitResult re n = trackingInitHandle->resultNum < maxResults ? trackingInitHandle->resultNum : maxResults; memcpy(results, trackingInitHandle->results, n * sizeof(TrackingInitResult)); *resultNum = n; + trackingInitHandle->scanPending = 0; return 1; } diff --git a/tests/webarkit_nft_test.cc b/tests/webarkit_nft_test.cc index 4578c05..69a330e 100644 --- a/tests/webarkit_nft_test.cc +++ b/tests/webarkit_nft_test.cc @@ -9,10 +9,13 @@ #include +#include #include #include #include #include +#include +#include namespace { @@ -60,6 +63,65 @@ TEST(MarkerDecompressTest, MalformedContentReturnsError) { std::remove("nft_test_bad.zft"); } +static bool fileExists(const std::string &path) { + std::ifstream in(path, std::ios::binary); + return in.good(); +} + +TEST(MarkerDecompressTest, ExtractsMarkersLargerThanFourMegabytes) { + const std::string iset(5 * 1024 * 1024, 0x41); + writeZft("nft_test_big", "{\"iset\":\"" + iset + "\",\"fset\":\"F\",\"fset3\":\"G\"}"); + + EXPECT_EQ(decompressMarkers("nft_test_big", "nft_test_big_out"), 0); + EXPECT_EQ(readFile("nft_test_big_out.iset"), iset); + EXPECT_EQ(readFile("nft_test_big_out.fset3"), "G"); + + std::remove("nft_test_big.zft"); + std::remove("nft_test_big_out.iset"); + std::remove("nft_test_big_out.fset"); + std::remove("nft_test_big_out.fset3"); +} + +TEST(MarkerDecompressTest, KeepsBinaryBytesUnchanged) { + const std::string bytes("A\nB\r\nC\x1a" "D", 8); + writeZft("nft_test_bin", "{\"iset\":\"" + bytes + "\",\"fset\":\"F\",\"fset3\":\"G\"}"); + + EXPECT_EQ(decompressMarkers("nft_test_bin", "nft_test_bin_out"), 0); + EXPECT_EQ(readFile("nft_test_bin_out.iset"), bytes); + + std::remove("nft_test_bin.zft"); + std::remove("nft_test_bin_out.iset"); + std::remove("nft_test_bin_out.fset"); + std::remove("nft_test_bin_out.fset3"); +} + +TEST(MarkerDecompressTest, FieldsOutOfOrderReturnErrorAndWriteNothing) { + writeZft("nft_test_order", "{\"iset\":\"I\",\"fset3\":\"G\",\"fset\":\"F\"}"); + EXPECT_EQ(decompressMarkers("nft_test_order", "nft_test_order_out"), -1); + EXPECT_FALSE(fileExists("nft_test_order_out.iset")); + std::remove("nft_test_order.zft"); +} + +TEST(MarkerDecompressTest, NonZlibDataReturnsError) { + { + std::ofstream out("nft_test_raw.zft", std::ios::binary); + out << "{\"iset\":\"I\",\"fset\":\"F\",\"fset3\":\"G\"}"; + } + EXPECT_EQ(decompressMarkers("nft_test_raw", "nft_test_raw_out"), -1); + EXPECT_FALSE(fileExists("nft_test_raw_out.iset")); + std::remove("nft_test_raw.zft"); +} + +TEST(MarkerDecompressTest, KeepsTheSourceArchive) { + writeZft("nft_test_keep", "{\"iset\":\"I\",\"fset\":\"F\",\"fset3\":\"G\"}"); + EXPECT_EQ(decompressMarkers("nft_test_keep", "nft_test_keep_out"), 0); + EXPECT_TRUE(fileExists("nft_test_keep.zft")); + std::remove("nft_test_keep.zft"); + std::remove("nft_test_keep_out.iset"); + std::remove("nft_test_keep_out.fset"); + std::remove("nft_test_keep_out.fset3"); +} + TEST(NFTMarkerStateTest, DefaultsToNotTracking) { NFTMarkerState state; EXPECT_FALSE(state.tracking); @@ -85,4 +147,30 @@ TEST(TrackingSubTest, StartsAndQuitsWorkerThread) { EXPECT_EQ(thread, nullptr); kpmDeleteHandle(&kpmHandle); } + +TEST(TrackingSubTest, RejectsStartUntilResultsAreCollected) { + KpmHandle *kpmHandle = kpmCreateHandle2(64, 48); + ASSERT_NE(kpmHandle, nullptr); + THREAD_HANDLE_T *thread = trackingInitInit(kpmHandle); + ASSERT_NE(thread, nullptr); + std::vector image(64 * 48, 128); + + EXPECT_EQ(trackingInitStart(thread, image.data()), 0); + EXPECT_EQ(trackingInitStart(thread, image.data()), -1); + + TrackingInitResult results[TRACKING_INIT_MAX_RESULTS]; + int resultNum = 0; + int ret; + while ((ret = trackingInitGetResults(thread, results, TRACKING_INIT_MAX_RESULTS, &resultNum)) == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + EXPECT_EQ(ret, 1); + EXPECT_EQ(trackingInitStart(thread, image.data()), 0); + + while (trackingInitGetResults(thread, results, TRACKING_INIT_MAX_RESULTS, &resultNum) == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + EXPECT_EQ(trackingInitQuit(&thread), 0); + kpmDeleteHandle(&kpmHandle); +} #endif