1aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk/*
2dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk * Copyright (C) 2011 The Android Open Source Project
3aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk *
4aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * Licensed under the Apache License, Version 2.0 (the "License");
5aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * you may not use this file except in compliance with the License.
6aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * You may obtain a copy of the License at
7aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk *
8aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk *      http://www.apache.org/licenses/LICENSE-2.0
9aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk *
10aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * Unless required by applicable law or agreed to in writing, software
11aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * distributed under the License is distributed on an "AS IS" BASIS,
12aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * See the License for the specific language governing permissions and
14aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk * limitations under the License.
15aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk */
16aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
17aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchoukpackage com.android.modelviewer;
18aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
1935ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopianimport android.renderscript.Matrix4f;
20aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchoukimport android.renderscript.RSSurfaceView;
21aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchoukimport android.renderscript.RenderScriptGL;
22aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
23aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchoukimport android.content.Context;
2435ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopianimport android.hardware.Sensor;
2535ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopianimport android.hardware.SensorEvent;
2635ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopianimport android.hardware.SensorEventListener;
2735ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopianimport android.hardware.SensorManager;
28aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchoukimport android.view.MotionEvent;
29dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchoukimport android.view.SurfaceHolder;
30dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchoukimport android.view.ScaleGestureDetector;
31dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchoukimport android.util.Log;
32aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
3335ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopianpublic class SimpleModelView extends RSSurfaceView implements SensorEventListener {
34aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
35aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    private RenderScriptGL mRS;
3632e09b5891da0174f161d99e2d3ebe67d6efa39cAlex Sakhartchouk    private SimpleModelRS mRender;
37aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
38dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    private ScaleGestureDetector mScaleDetector;
39aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
4035ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    private SensorManager mSensorManager;
4135ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    private Sensor mRotationVectorSensor;
4235ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    private final float[] mRotationMatrix = new float[16];
4335ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian
44dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    private static final int INVALID_POINTER_ID = -1;
45dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    private int mActivePointerId = INVALID_POINTER_ID;
4635ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    private boolean mUseSensor = false;
4735ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    private Matrix4f mIdentityMatrix = new Matrix4f();
48dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
49dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    public SimpleModelView(Context context) {
50dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        super(context);
51dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        ensureRenderScript();
52dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
5335ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        // Get an instance of the SensorManager
5435ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        mSensorManager = (SensorManager)getContext().getSystemService(Context.SENSOR_SERVICE);
5535ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        // find the rotation-vector sensor
5635ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        mRotationVectorSensor = mSensorManager.getDefaultSensor(
5735ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                Sensor.TYPE_ROTATION_VECTOR);
5835ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        mIdentityMatrix.loadIdentity();
59dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    }
60dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
61dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    private void ensureRenderScript() {
62aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk        if (mRS == null) {
632222aa90031604e9752ebc2909303a84139b8b18Jason Sams            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
642222aa90031604e9752ebc2909303a84139b8b18Jason Sams            sc.setDepth(16, 24);
65bf6ef8d78fffbce6c1849a4a28fb3f4401ad039eJason Sams            mRS = createRenderScriptGL(sc);
6632e09b5891da0174f161d99e2d3ebe67d6efa39cAlex Sakhartchouk            mRender = new SimpleModelRS();
67dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            mRender.init(mRS, getResources());
68aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk        }
69aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    }
70aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
71aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    @Override
7235ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    public void resume() {
7335ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        mSensorManager.registerListener(this, mRotationVectorSensor, 10000);
7435ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    }
7535ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian
7635ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    @Override
7735ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    public void pause() {
7835ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        mSensorManager.unregisterListener(this);
7935ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    }
8035ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian
8135ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    @Override
82dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    protected void onAttachedToWindow() {
83dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        super.onAttachedToWindow();
84dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        ensureRenderScript();
85dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    }
86dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
87dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    @Override
88dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
89dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        super.surfaceChanged(holder, format, w, h);
90dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        mRender.surfaceChanged();
91dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    }
92dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
93dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    @Override
94aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    protected void onDetachedFromWindow() {
95dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        mRender = null;
96ed9f210568082dd6d1d8a0c92c693d574d87d545Alex Sakhartchouk        if (mRS != null) {
97aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk            mRS = null;
98bf6ef8d78fffbce6c1849a4a28fb3f4401ad039eJason Sams            destroyRenderScriptGL();
99aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk        }
100aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    }
101aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
102dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    public void loadA3DFile(String path) {
103dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        mRender.loadA3DFile(path);
104aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    }
105aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
106aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    @Override
107dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    public boolean onTouchEvent(MotionEvent ev) {
108dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        mScaleDetector.onTouchEvent(ev);
109dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
110dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        boolean ret = false;
111dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        float x = ev.getX();
112dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        float y = ev.getY();
113dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
114dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        final int action = ev.getAction();
115dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
116dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        switch (action & MotionEvent.ACTION_MASK) {
117dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        case MotionEvent.ACTION_DOWN: {
118dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            mRender.onActionDown(x, y);
119dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            mActivePointerId = ev.getPointerId(0);
120dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            ret = true;
121dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            break;
122dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        }
123dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        case MotionEvent.ACTION_MOVE: {
124dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            if (!mScaleDetector.isInProgress()) {
125dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                mRender.onActionMove(x, y);
126dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            }
127dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            mRender.onActionDown(x, y);
128dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            ret = true;
129dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            break;
130dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        }
131dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
132dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        case MotionEvent.ACTION_UP: {
133dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            mActivePointerId = INVALID_POINTER_ID;
134dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            break;
135dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        }
136dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
137dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        case MotionEvent.ACTION_CANCEL: {
138dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            mActivePointerId = INVALID_POINTER_ID;
139dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            break;
140dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        }
141dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
142dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        case MotionEvent.ACTION_POINTER_UP: {
143dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
144dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
145dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            final int pointerId = ev.getPointerId(pointerIndex);
146dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            if (pointerId == mActivePointerId) {
147dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                // This was our active pointer going up. Choose a new
148dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                // active pointer and adjust accordingly.
149dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
150dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                x = ev.getX(newPointerIndex);
151dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                y = ev.getY(newPointerIndex);
152dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                mRender.onActionDown(x, y);
153dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk                mActivePointerId = ev.getPointerId(newPointerIndex);
154dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            }
155dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            break;
156dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        }
157aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk        }
158aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
159aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk        return ret;
160aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk    }
161dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk
162dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
163dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        @Override
164dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        public boolean onScale(ScaleGestureDetector detector) {
165dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            mRender.onActionScale(detector.getScaleFactor());
166dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk            return true;
167dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk        }
168dc165b3365ca3e8fd7c4eb11b1154646977a6d0aAlex Sakhartchouk    }
169aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
17035ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    public void onSensorChanged(SensorEvent event) {
17135ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        // we received a sensor event. it is a good practice to check
17235ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        // that we received the proper event
17335ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        if (mUseSensor) {
17435ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian            if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
17535ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                // convert the rotation-vector to a 4x4 matrix. the matrix
17635ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                // is interpreted by Open GL as the inverse of the
17735ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                // rotation-vector, which is what we want.
17835ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                SensorManager.getRotationMatrixFromVector(
17935ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                        mRotationMatrix , event.values);
18035ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian
18135ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                if (mRender != null) {
18235ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                    mRender.onPostureChanged(new Matrix4f(mRotationMatrix));
18335ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian                }
18435ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian            }
18535ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        }
18635ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    }
18735ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian
18835ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    public void onAccuracyChanged(Sensor sensor, int accuracy) {
18935ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    }
190aae74ad6144470c66e72b075ac3afeddb186fa98Alex Sakhartchouk
19135ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    public void toggleSensor() {
19235ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        mUseSensor = !mUseSensor;
19335ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        if (mUseSensor == false) {
19435ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian            mRender.onPostureChanged(mIdentityMatrix);
19535ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian        }
19635ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian    }
19735ccf46533c76cdc7c2f6c0ce8f33b34b29bc5e6Mathias Agopian}
198