2015年9月11日星期五

CSharpGL(5)解析3DS文件并用CSharpGL渲染 - BIT祝威

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
CSharpGL(5)解析3DS文件并用CSharpGL渲染 - BIT祝威  阅读原文»

CSharpGL(5)解析3DS文件并用CSharpGL渲染

我曾经写过一个简单的*.3ds文件的解析器,但是只能解析最基本的顶点、索引信息,且此解析器是仿照别人的C++代码改写的,设计的也不好,不足方便扩展。

现在我重新设计实现了一个*.3ds文件的解析器,它能解析的Chunk类型更多,且容易扩展。以后需要解析更多类型的Chunk时比较简单。

+BIT祝威+悄悄在此留下版了个权的信息说:

下载

这个3DS解析器是CSharpGL的一部分,CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

本文所用的3ds文件您可以在此(http://www.cgrealm.org/d/downpage.php?n=2&id=15764::1326768548)下载,由于文件比较大我就不上传了。

+BIT祝威+悄悄在此留下版了个权的信息说:

3DS文件格式

3ds文件是二进制的。3ds格式的基本单元叫块(chunk)。我们就是读这样一块一块的信息。目录树如下,缩进风格体现了块的父子关系。可见3ds模型文件和XML文件类似,都是只有1个根结点的树状结构。

1 0x4D4D // Main Chunk
2 ├─ 0x0002 // M3D Version
3 ├─ 0x3D3D // 3D Editor Chunk
4 │ ├─ 0x4000 // Object Block
5 │ │ ├─ 0x4100 // Triangular Mesh
6 │ │ │ ├─ 0x4110 // Vertices List
7 │ │ │ ├─ 0x4120 // Faces Description
8 │ │ │ │ ├─ 0x4130 // Faces Material
9 │ │ │ │ └─ 0x4150 // Smoothing Group List
10 │ │ │ ├─ 0x4140 // Mapping Coordinates List
11 │ │ │ └─ 0x4160 // Local Coordinates System
12 │ │ ├─ 0x4600 // Light
13 │ │ │ └─ 0x4610 // Spotlight
14 │ │ └─ 0x4700 // Camera
15 │ └─ 0xAFFF // Material Block
16 │ ├─ 0xA000 // Material Name
17 │ ├─ 0xA010 // Ambient Color
18 │ ├─ 0xA020 // Diffuse Color
19 │ ├─ 0xA030 // Specular Color
20 │ ├─ 0xA200 // Texture Map 1
21 │ ├─ 0xA230 // Bump Map
22 │ └─ 0xA220 // Reflection Map
23 │ │ // Sub Chunks For Each Map
24 │ ├─ 0xA300 // Mapping Filename
25 │ └─ 0xA351 // Mapping Parameters
26 └─ 0xB000 // Keyframer Chunk
27 ├─ 0xB002 // Mesh Information Block
28 ├─ 0xB007 // Spot Light Information Block
29 └─ 0xB008 // Frames (Start and End)
30 ├─ 0xB010 // Object Name
31 ├─ 0xB013 // Object Pivot Point
32 ├─ 0xB020 // Position Track
33 ├─ 0xB021 // Rotation Track
34 ├─ 0xB022 // Scale Track
35 └─ 0xB030 // Hierarchy Position

实际上完整的chunk列表有上千种类型,我们只需解析其中的顶点列表、面列表和纹理UV列表就行了。

以类型标识为0x4D4D的MAIN CHUNK为例,整个3ds文件的前两个byte必须是0x4D4D,否则就说明这个文件不是3ds模型文件。然后从第3到第6个byte是一个Uint32型的数值,表示整个MAIN CHUNK的长度。由于MAIN CHUNK是整个3ds文件的根结点,它的长度也即整个3ds文件的长度。

块(Chunk)的结构

每一个“chunk”的结构如下所示:

偏移量

长度

0

2

块标识符

2

4

块长: 块数据 + 子块内容

6

n

块数据

6+n

m

S子块

文件内容

一个3DS文件,其中包含若干材质对象,材质对象里有材质参数和贴图文件名;还有若干子模型,每个子模型都由顶点位置、UV位置、三角形索引和分组索引构成。分组索引是这么一个东西:它由若干三角形索引的编号和一个材质对象名组成。这个分组索引似乎暗示着:渲染过程应根据分组索引描绘的顺序进行,即取出一个分组索引,绑定它指定的材质和贴图,渲染它指定的三角形,然后取出下一个分组索引继续上述渲染操作。我们将在后文进行验证。

+BIT祝威+悄悄在此留下版了个权的信息说:

解析器设计思路

【轻松一刻】实战项目开发(二) list数据去重 数据追加与缓存 - sphere  阅读原文»

引入开源控件 PullToRefresh 下拉刷新列表

每次下拉刷新都会发送请求,从接口返回json信息。

如果前后两次请求返回的数据中有重复的数据 该怎么给list去重

在上一篇中我们重写了实体Data的hashcode和equals方法

/**
* 因为更新时间和unixtime都不是唯一的
* 这里使用唯一标识hashId来得到哈希码
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result
= prime * result + ((hashId == null) ? 0 : hashId.hashCode());
return result;
}

/**
* 因为更新时间和unixtime都不是唯一的
* 这里使用唯一标识hashId来比较
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Data other
= (Data) obj;
if (hashId == null) {
if (other.hashId != null)
return false;
}
else if (!hashId.equals(other.hashId))
return false;
return true;
}

因为data数据中只有hashId 是唯一标识,所以我们使用它做比较

使用set中不能添加重新元素的特性作为判断条件去重

/**
* 重写Data中的hashCode和equals方法
* 使用set中不能添加重新元素的特性作为判断条件
* 将不重复的data元素依次放入临时的newlist
* 循环完毕后,将原始list清空,addAll(newlist)
*
@param list
*
@return
*/
public static List<Data> removeDuplicateDataInOrder(List<Data> list)
{
HashSet
<Data> hashSet = new HashSet<Data>();
List
<Data> newlist = new ArrayList<Data>();
for (Iterator iterator = list.iterator(); iterator.hasNext();)
{
Data element
= (Data) iterator.next();
if (hashSet.add(element))
{
newlist.add(element);
}
}
list.clear();
list.addAll(newlist);
return list;
}

而为了保证数据排列的先后顺序 我们应该在去重复之前做如下操作

1.如果是下拉刷新 在把数据添加到list的头部 list.addAll(0,list<T>)
2.如果是上拉加载更多则把数据加载到尾部 list.addAll(list<T>)

聚合数据接口 还可以按时间戳返回该时间点前或后的笑话列表

请求示例:http://japi.juhe.cn/joke/content/list.from?key=您申请的KEY&page=2&pagesize=10&sort=asc&time=1418745237

其中参数中的sort param sort desc:指定时间之前发布的,asc:指定时间之后发布的 ,time 表示时间戳

所以我们在上拉加载更多时 使用这种请求方式,以当前list中最后一条数据的时间戳为节点 返回该时间点前笑话列表。

故上拉加载更多时,list没有必要再去重复了、

if(flag == PULL_DOWN_REFRESH_FLAG){
// 将新刷新的数据 添加到数据集头部
mCurrentListItems.addAll(0,result.getResult());
// 去除list中重复的数据
mCurrentListItems = UtilsHelper.removeDuplicateDataInOrder(mCurrentListItems);
}
else if(flag == PULL_UP_REFRESH_FLAG){
// 将新加载的数据 添加到数据集尾部
// 因为是以最后一条数据的时间戳向前查询,故不存在重复,无需list去重
mCurrentListItems.addAll(result.getResult());
pullUpPageNumber
++;
}

如何得到本次请求之后更新了多少条数据呢,很简单只要保存上次的list,并与最新的list的size做对比即可。

使用handler处理,并在主线程更新UI

if(mLastListItems != null){
int count = mCurrentListItems.size() - mLastListItems.size();

android.os.Message msg
= new android.os.Message();
msg.what
= UPDATE_DATA_COUNT_MESSAGE;
msg.arg1
= count;
mHandler.sendMessage(msg);
}

然后通知adapter数据集发生改变,并调用listview (listview是PullToRefreshListView的实例) 的onRefreshComplete()方法

// 通知程序数据集已经改变,如果不做通知,那么将不会刷新mListItems的集合
mAdapter.notifyDataSetChanged();
mListView.onRefreshComplete();

假设我们处于没有网络的环境,那就有必要搞一个缓存了,以便离线查看,最好是缓存上次我们退出应用时显示的list数据。

没有使用sqlite,暂时使用文件,缓存到sdcard的一个目录中(/storage/emulated/0/qingsongyike/cache)

public static void saveJsonTextInLocalFile(String jsonData){
boolean sdCardExist = Environment.getExternalStorageState()
.equals(android.os.Environment.MEDIA_MOUNTED);
//判断sd卡是否存在
if(sdCardExist){
Log.d(
"UtilsHelper", "sdcard exist.");
File root
= Environment.getExternalStorageDirectory();
String path
= root.getPath()+File.separator+"qingsongyike"+File.separator+"cache"阅读更多内容

没有评论:

发表评论