artoolkitx的demo只是简单的渲染了一个方块,想要复杂的3D模型,最好能有动画的,最初想用assimp的,可是太复杂了,我自己还搞不懂,就找其他的,发现了个纯java的3D引擎rajawali,嗯还是有点复杂要仔细研究,最后找到个简单的android-3D-model-viewer,然后下载下来东改改,西改改勉强可以用了,先看效果
用的是android-3D-model-viewer(下边我就叫它modelview了)里的cowboy.dae,改的太多了,说说注意事项和问题吧
1.artoolkitx把东西都集成在了ARActivity内,只提供了个supplyRenderer函数来添加ARRenderer,很不自由,而且摄像头启动的很慢,还要深入研究,把ARActivity拆开,能自由的添加东西,优化速度
2.artoolkitx的矩阵已经把摄像头的位置矩阵和模型的位置矩阵已经合并了,然后我对矩阵又很陌生,modelview里有个摄像头光源,我就没法设置了,然后出来的模型就比较暗
3.模型大小问题,modelview会自适应模型大小,通过计算生成相应的尺寸的矩阵,其实还是矩阵问题,artoolkitx就一个矩阵,不知道怎么和modelview的矩阵相互合并,头疼,所以每换一个模型就手动一点一点设置一个合适的尺寸,我在我自定义的ModelRenderer的onDrawFrame内添加了设置模型大小的代码
float scale = 20;
objData.setScale(new float[]{scale,scale,scale});
4.modelview有些参数设置了全局静态的,不小心漏了就会导致模型加载不出来,坑死了
好了贴一下我改的代码吧
MainApplication.java
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
AssetHelper assetHelper = new AssetHelper(getAssets());
assetHelper.cacheAssetFolder(this, "Data");
assetHelper.cacheAssetFolder(this, "cparam_cache");
}
}
MainActivity.java
public class MainActivity extends ARActivity {
static {
System.setProperty("java.protocol.handler.pkgs", "org.andresoviedo.util.android");
URL.setURLStreamHandlerFactory(new AndroidURLStreamHandlerFactory());
}
private SceneLoader scene;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ContentUtils.provideAssets(this);
scene = new SceneLoader(this, Uri.parse("assets://" + getPackageName() + "/models/cowboy.dae"));
scene.init();
}
/**
* Provide our own ARSquareTrackingRenderer.
*/
@Override
protected ARRenderer supplyRenderer() {
return new ModelRenderer(this,scene);
}
/**
* Use the FrameLayout in this Activity's UI.
*/
@Override
protected FrameLayout supplyFrameLayout() {
return (FrameLayout) this.findViewById(R.id.mainFrameLayout);
}
}
ModelRenderer.java
public class ModelRenderer extends ARRenderer {
private final static String TAG = ModelRenderer.class.getName();
/**
* Add 0.5f to the alpha component to the global shader so we can see through the skin
*/
private static final float[] BLENDING_FORCED_MASK_COLOR = {1.0f, 1.0f, 1.0f, 0.5f};
// width of the screen
private int width;
// height of the screen
private int height;
/**
* Drawer factory to get right renderer/shader based on object attributes
*/
private DrawerFactory drawer;
// The loaded textures
private Map
SceneLoader.java
public class SceneLoader implements LoaderTask.Callback {
private Activity activity;
private Uri paramUri;
/**
* List of data objects containing info for building the opengl objects
*/
private List objects = new ArrayList<>();
/**
* Animate model (dae only) or not
*/
private boolean doAnimation = true;
/**
* Animator
*/
private Animator animator = new Animator();
/**
* time when model loading has started (for stats)
*/
private long startTime;
public SceneLoader(Activity activity,Uri paramUri) {
this.activity = activity;
this.paramUri = paramUri;
}
public void init() {
if (paramUri == null){
return;
}
startTime = SystemClock.uptimeMillis();
Uri uri = paramUri;
Log.i("Object3DBuilder", "Loading model " + uri + ". async and parallel..");
if (uri.toString().toLowerCase().endsWith(".obj")) {
new WavefrontLoaderTask(activity, uri, this).execute();
} else if (uri.toString().toLowerCase().endsWith(".stl")) {
Log.i("Object3DBuilder", "Loading STL object from: "+uri);
new STLLoaderTask(activity, uri, this).execute();
} else if (uri.toString().toLowerCase().endsWith(".dae")) {
Log.i("Object3DBuilder", "Loading Collada object from: "+uri);
new ColladaLoaderTask(activity, uri, this).execute();
}
}
/**
* Hook for animating the objects before the rendering
*/
public void onDrawFrame() {
if (objects.isEmpty()) return;
if (doAnimation) {
for (int i=0; i newList = new ArrayList(objects);
newList.add(obj);
this.objects = newList;
}
public synchronized List getObjects() {
return objects;
}
public boolean isDoAnimation() {
return doAnimation;
}
/**
* Whether to draw using textures
*/
private boolean drawTextures = true;
public boolean isDrawTextures() {
return drawTextures;
}
@Override
public void onStart(){
ContentUtils.setThreadActivity(activity);
if(listener != null){
listener.onStart();
}
}
@Override
public void onLoadComplete(List datas) {
// TODO: move texture load to LoaderTask
for (Object3DData data : datas) {
if (data.getTextureData() == null && data.getTextureFile() != null) {
Log.i("LoaderTask","Loading texture... "+data.getTextureFile());
try (InputStream stream = ContentUtils.getInputStream(data.getTextureFile())){
if (stream != null) {
data.setTextureData(IOUtils.read(stream));
}
} catch (IOException ex) {
data.addError("Problem loading texture " + data.getTextureFile());
}
}
}
// TODO: move error alert to LoaderTask
List allErrors = new ArrayList<>();
for (Object3DData data : datas) {
addObject(data);
allErrors.addAll(data.getErrors());
}
if (!allErrors.isEmpty()){
Log.e("SceneLoader", allErrors.toString());
// makeToastText(allErrors.toString(), Toast.LENGTH_LONG);
}
final String elapsed = (SystemClock.uptimeMillis() - startTime) / 1000 + " secs";
Log.i("LoaderTask","Build complete (" + elapsed + ")");
ContentUtils.setThreadActivity(null);
if(listener != null){
listener.onLoadComplete(this);
}
}
@Override
public void onLoadError(Exception ex) {
Log.e("SceneLoader", ex.getMessage(), ex);
ContentUtils.setThreadActivity(null);
if(listener != null){
listener.onLoadError(ex);
}
}
/**
* Enable or disable blending (transparency)
*/
private boolean isBlendingEnabled = true;
public boolean isBlendingEnabled() {
return isBlendingEnabled;
}
/**
* Force transparency
*/
private boolean isBlendingForced = false;
public boolean isBlendingForced() {
return isBlendingForced;
}
/**
* Whether to draw using colors or use default white color
*/
private boolean drawColors = true;
public boolean isDrawColors() {
return drawColors;
}
/**
* Light toggle feature: whether to draw using lights
*/
private boolean drawLighting = true;
public boolean isDrawLighting() {
return drawLighting;
}
private OnSceneLoaderListener listener;
public void setOnSceneLoaderListener(OnSceneLoaderListener listener) {
this.listener = listener;
}
public interface OnSceneLoaderListener {
void onStart();
void onLoadError(Exception ex);
void onLoadComplete(SceneLoader scene);
}
}
modelview的SceneLoader改的比较多,导致LoaderTask内的dialog报错,所以就把关于dialog的代码都注释了
LoaderTask.java
public abstract class LoaderTask extends AsyncTask> {
/**
* URL to the 3D model
*/
protected final Uri uri;
/**
* Callback to notify of events
*/
private final Callback callback;
/**
* The dialog that will show the progress of the loading
*/
// private final ProgressDialog dialog;
/**
* Build a new progress dialog for loading the data model asynchronously
* @param uri the URL pointing to the 3d model
*
*/
public LoaderTask(Activity parent, Uri uri, Callback callback) {
this.uri = uri;
// this.dialog = ProgressDialog.show(this.parent, "Please wait ...", "Loading model data...", true);
// this.dialog.setTitle(modelId);
// this.dialog = new ProgressDialog(parent);
this.callback = callback; }
@Override
protected void onPreExecute() {
super.onPreExecute();
// this.dialog.setMessage("Loading...");
// this.dialog.setCancelable(false);
// this.dialog.show();
}
@Override
protected List doInBackground(Void... params) {
try {
callback.onStart();
List data = build();
build(data);
callback.onLoadComplete(data);
return data;
} catch (Exception ex) {
callback.onLoadError(ex);
return null;
}
}
protected abstract List build() throws Exception;
protected abstract void build(List data) throws Exception;
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// switch (values[0]) {
// case 0:
// this.dialog.setMessage("Analyzing model...");
// break;
// case 1:
// this.dialog.setMessage("Allocating memory...");
// break;
// case 2:
// this.dialog.setMessage("Loading data...");
// break;
// case 3:
// this.dialog.setMessage("Scaling object...");
// break;
// case 4:
// this.dialog.setMessage("Building 3D model...");
// break;
// case 5:
// // Toast.makeText(parent, modelId + " Build!", Toast.LENGTH_LONG).show();
// break;
// }
}
@Override
protected void onPostExecute(List data) {
super.onPostExecute(data);
// if (dialog.isShowing()) {
// dialog.dismiss();
// }
}
public interface Callback {
void onStart();
void onLoadError(Exception ex);
void onLoadComplete(List data);
}
}
脑壳痛
2020/4/27更新
添加了多模型展示,对ModelRenderer和SceneLoader进行了修改,增加了模型旋转功能
没旋转的时候是这样的
旋转后是
在ModelRenderer.java的configureARScene内进行model的设置
/**
* 模型名字
*/
private static final String modelNames[] = new String[]{
"cowboy.dae",
"ToyPlane.obj"
};
/**
* 缩放比例
*/
private static final float modelScales[] = new float[]{
20,
100
};
private SparseArray scenes = new SparseArray<>();
/**
* Markers can be configured here.
*/
@Override
public boolean configureARScene() {
int i = 0;
for (Trackable trackable : trackables) {
trackableUIDs[i] = ARController.getInstance().addTrackable("single;Data/" + trackable.getName() + ".patt;" + trackable.getWidth());
if (trackableUIDs[i] < 0) return false;
SceneLoader scene = new SceneLoader(activity, Uri.parse("assets://" + activity.getPackageName() + "/models/"+modelNames[i]));
scene.setModelScale(modelScales[i]);
scene.init();
if(i == 1){
scene.setModelRotation(new float[]{0,90,0});
}
scenes.put(trackableUIDs[i],scene);
i++;
}
return true;
}
2020/4/27再更新
花了些时间把摄像头启动很慢的问题给解决了,虽然不知道那段代码是干什么的
修改ARX\ARVideo文件夹下的cparamSearch.c代码,找到函数static void *cparamSearchWorker(THREAD_HANDLE_T *threadHandle),修改代码
curlErr = curl_easy_perform(curlHandle);
if (curlErr != CURLE_OK) {
// No need to report error, since we expect it (e.g.) when wifi and cell data are off.
// Typical first error in these cases is failure to resolve the hostname.
//ARLOGe("Error performing CURL network test: %s (%d). %s.\n", curl_easy_strerror(curlErr), curlErr, curlErrorBuf);
result = CPARAM_SEARCH_STATE_FAILED_NO_NETWORK;
}
改成
// curlErr = curl_easy_perform(curlHandle);
// if (curlErr != CURLE_OK) {
// // No need to report error, since we expect it (e.g.) when wifi and cell data are off.
// // Typical first error in these cases is failure to resolve the hostname.
// //ARLOGe("Error performing CURL network test: %s (%d). %s.\n", curl_easy_strerror(curlErr), curlErr, curlErrorBuf);
result = CPARAM_SEARCH_STATE_FAILED_NO_NETWORK;
// }
改完后基本秒开
详细还是看demo吧
GitHub