@threepipe/plugin-blend-importer 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BlendLoadPlugin.d.ts +1 -1
- package/dist/BlendLoadPlugin.d.ts.map +1 -1
- package/dist/dependencies.txt +32 -1
- package/dist/index.js +2179 -82
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2179 -81
- package/dist/index.mjs.map +1 -1
- package/dist/loader/bakeGetters.d.ts +2 -0
- package/dist/loader/bakeGetters.d.ts.map +1 -0
- package/dist/loader/camera.d.ts +4 -0
- package/dist/loader/camera.d.ts.map +1 -0
- package/dist/loader/ctx.d.ts +16 -0
- package/dist/loader/ctx.d.ts.map +1 -0
- package/dist/loader/geometry.d.ts +3 -2
- package/dist/loader/geometry.d.ts.map +1 -1
- package/dist/loader/index.d.ts +2 -1
- package/dist/loader/index.d.ts.map +1 -1
- package/dist/loader/light.d.ts +3 -2
- package/dist/loader/light.d.ts.map +1 -1
- package/dist/loader/material.d.ts +2 -2
- package/dist/loader/material.d.ts.map +1 -1
- package/dist/loader/mesh.d.ts +2 -2
- package/dist/loader/mesh.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/BlendLoadPlugin.ts +42 -3
- package/src/js-blend/main.js +3 -2
- package/src/js-blend/parser/parser.js +152 -24
- package/src/js-blend/threejs/blend_three.js +1 -1
- package/src/js-blend/threejs/light.js +1 -1
- package/src/js-blend/threejs/material.js +1 -1
- package/src/loader/bakeGetters.ts +63 -0
- package/src/loader/camera.ts +55 -0
- package/src/loader/ctx.ts +29 -0
- package/src/loader/geometry.ts +236 -13
- package/src/loader/index.ts +95 -6
- package/src/loader/light.ts +18 -13
- package/src/loader/material.ts +7 -5
- package/src/loader/mesh.ts +14 -13
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {Object3D} from 'threepipe'
|
|
2
|
+
import {Ctx} from './ctx'
|
|
3
|
+
|
|
4
|
+
export function createCamera(object: any, ctx: Ctx): Object3D | undefined {
|
|
5
|
+
const cdata = object.data
|
|
6
|
+
// console.log(bakeGetters(object))
|
|
7
|
+
if (!cdata) return undefined
|
|
8
|
+
|
|
9
|
+
// Blender camera types: 0 = Perspective, 1 = Orthographic
|
|
10
|
+
const type = cdata.type
|
|
11
|
+
let camera: Object3D
|
|
12
|
+
if (type === 0) {
|
|
13
|
+
// Perspective
|
|
14
|
+
// Convert lens (mm) to field of view (fov) in degrees (vfov)
|
|
15
|
+
const sensorWidth = cdata.sensor_x || 36
|
|
16
|
+
const sensorHeight = cdata.sensor_y || 24
|
|
17
|
+
const lens = cdata.lens || 50
|
|
18
|
+
const fov = 2 * Math.atan(sensorHeight / (2 * lens)) * (180 / Math.PI)
|
|
19
|
+
|
|
20
|
+
camera = new ctx.PerspectiveCamera(
|
|
21
|
+
fov,
|
|
22
|
+
sensorWidth / sensorHeight,
|
|
23
|
+
cdata.clipsta || 0.1,
|
|
24
|
+
cdata.clipend || 1000
|
|
25
|
+
)
|
|
26
|
+
if ('shiftx' in cdata) camera.userData.shiftX = cdata.shiftx // todo?
|
|
27
|
+
if ('shiftx' in cdata) camera.userData.shiftX = cdata.shiftx // todo?
|
|
28
|
+
if ('shifty' in cdata) camera.userData.shiftY = cdata.shifty
|
|
29
|
+
} else if (type === 1) {
|
|
30
|
+
// Orthographic
|
|
31
|
+
const scale = cdata.ortho_scale || 1
|
|
32
|
+
const aspect = (cdata.sensor_x || 36) / (cdata.sensor_y || 24)
|
|
33
|
+
const left = -scale * aspect / 2
|
|
34
|
+
const right = scale * aspect / 2
|
|
35
|
+
const top = scale / 2
|
|
36
|
+
const bottom = -scale / 2
|
|
37
|
+
camera = new ctx.OrthographicCamera(
|
|
38
|
+
left, right, top, bottom,
|
|
39
|
+
cdata.clipsta || 0.1,
|
|
40
|
+
cdata.clipend || 1000
|
|
41
|
+
)
|
|
42
|
+
if ('shiftx' in cdata) camera.userData.shiftX = cdata.shiftx
|
|
43
|
+
if ('shifty' in cdata) camera.userData.shiftY = cdata.shifty
|
|
44
|
+
} else {
|
|
45
|
+
// Unknown camera type
|
|
46
|
+
camera = new ctx.PerspectiveCamera()
|
|
47
|
+
console.warn('Unsupported camera type', type)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// const obj = new Object3D()
|
|
51
|
+
// camera.rotation.x = -Math.PI / 2
|
|
52
|
+
// obj.add(camera)
|
|
53
|
+
|
|
54
|
+
return camera
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AmbientLight,
|
|
3
|
+
BufferAttribute,
|
|
4
|
+
BufferGeometry,
|
|
5
|
+
DirectionalLight,
|
|
6
|
+
Mesh,
|
|
7
|
+
MeshBasicMaterial,
|
|
8
|
+
MeshPhysicalMaterial,
|
|
9
|
+
Object3D,
|
|
10
|
+
OrthographicCamera,
|
|
11
|
+
PerspectiveCamera,
|
|
12
|
+
PointLight,
|
|
13
|
+
SpotLight,
|
|
14
|
+
} from 'threepipe'
|
|
15
|
+
|
|
16
|
+
export interface Ctx {
|
|
17
|
+
PointLight: typeof PointLight
|
|
18
|
+
SpotLight: typeof SpotLight
|
|
19
|
+
DirectionalLight: typeof DirectionalLight
|
|
20
|
+
AmbientLight: typeof AmbientLight
|
|
21
|
+
Object3D: typeof Object3D
|
|
22
|
+
PerspectiveCamera: typeof PerspectiveCamera
|
|
23
|
+
OrthographicCamera: typeof OrthographicCamera
|
|
24
|
+
Mesh: typeof Mesh
|
|
25
|
+
BufferGeometry: typeof BufferGeometry
|
|
26
|
+
BufferAttribute: typeof BufferAttribute
|
|
27
|
+
MeshPhysicalMaterial: typeof MeshPhysicalMaterial
|
|
28
|
+
MeshBasicMaterial: typeof MeshBasicMaterial
|
|
29
|
+
}
|
package/src/loader/geometry.ts
CHANGED
|
@@ -1,13 +1,227 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Ctx} from './ctx'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function getLayer(layers: any, i: number) {
|
|
4
|
+
if (!Array.isArray(layers)) return layers
|
|
5
|
+
return layers[i]
|
|
6
|
+
}
|
|
7
|
+
// https://projects.blender.org/blender/blender/pulls/108015/files
|
|
8
|
+
// https://projects.blender.org/blender/blender/commit/1b63a290c68636211b16c5e212a699e6b63031b9
|
|
9
|
+
// https://developer.blender.org/docs/release_notes/4.0/python_api/#breaking-changes
|
|
10
|
+
// https://developer.blender.org/docs/release_notes/4.0/#blend-files
|
|
11
|
+
// https://projects.blender.org/blender/blender/commit/7966cd16d6dc4e66d01f7bd68a090107c1a7978c
|
|
12
|
+
// https://projects.blender.org/blender/blender/issues/95967
|
|
13
|
+
// https://projects.blender.org/blender/blender/pulls/106638
|
|
14
|
+
|
|
15
|
+
// https://github.com/blender/blender/blob/55e2fd2929b7577e0785c128c8f8069efd990c07/source/blender/blenkernel/intern/mesh.cc#L413
|
|
16
|
+
export function createBufferGeometry(meshData: any, ctx: Ctx) {
|
|
17
|
+
|
|
18
|
+
if (meshData.mpoly) return createBufferGeometryOld(meshData, ctx)
|
|
19
|
+
const geometry = new ctx.BufferGeometry()
|
|
20
|
+
geometry.name = meshData.aname || ''
|
|
21
|
+
|
|
22
|
+
// console.log(bakeGetters(meshData))
|
|
23
|
+
|
|
24
|
+
// https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L19
|
|
25
|
+
|
|
26
|
+
let vertices
|
|
27
|
+
let indices
|
|
28
|
+
|
|
29
|
+
// Extract vertex positions from vdata layers
|
|
30
|
+
if (meshData.vdata && meshData.vdata.layers && meshData.vdata.totlayer > 0) {
|
|
31
|
+
for (let i = 0; i < meshData.vdata.totlayer; i++) {
|
|
32
|
+
const layer = getLayer(meshData.vdata.layers, i)
|
|
33
|
+
// if (layer.type === 0) {
|
|
34
|
+
// https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L1575
|
|
35
|
+
const data = layer.data || []
|
|
36
|
+
// if (layer.name === 'position') { // type = 48 (custom vec3)
|
|
37
|
+
if (data.length === meshData.totvert) {
|
|
38
|
+
if (vertices && (layer.name !== 'position' || vertices.name === 'position')) {
|
|
39
|
+
// console.warn('BlendLoader - multiple vertices, ignoring', layer)
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
vertices = layer
|
|
43
|
+
} else {
|
|
44
|
+
// console.log('unknown vdata', layer)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Blender loops are indexes in three.js/blender. Called dots in gltf exporter
|
|
50
|
+
|
|
51
|
+
// // Extract indexes from loop data layers
|
|
52
|
+
if (meshData.ldata && meshData.ldata.layers) {
|
|
53
|
+
for (let i = 0; i < meshData.ldata.totlayer; i++) {
|
|
54
|
+
const layer = getLayer(meshData.ldata.layers, i)
|
|
55
|
+
// if (layer.type === 0) {
|
|
56
|
+
const data = layer.data || []
|
|
57
|
+
// if (layer.name === '.corner_vert') { // type = 11
|
|
58
|
+
if (data.length === meshData.totloop) { // type = 11
|
|
59
|
+
if (indices && (layer.name !== '.corner_vert' || indices.name === '.corner_vert')) {
|
|
60
|
+
// console.warn('BlendLoader - multiple indices, ignoring', layer)
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
indices = layer
|
|
64
|
+
} else {
|
|
65
|
+
// console.log('unknown ldata', layer)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// if (meshData.pdata && meshData.pdata.layers) {
|
|
70
|
+
// for (let i = 0; i < meshData.pdata.totlayer; i++) {
|
|
71
|
+
// const layer = getLayer(meshData.pdata.layers, i)
|
|
72
|
+
// // if (layer.type === 0) {
|
|
73
|
+
// // if (layer.name === '.corner_vert') { // type = 11
|
|
74
|
+
// // } else {
|
|
75
|
+
// console.log('unknown pdata', layer)
|
|
76
|
+
// // }
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
// if (meshData.fdata && meshData.fdata.layers) {
|
|
80
|
+
// for (let i = 0; i < meshData.fdata.totlayer; i++) {
|
|
81
|
+
// const layer = getLayer(meshData.fdata.layers, i)
|
|
82
|
+
// // if (layer.type === 0) {
|
|
83
|
+
// // if (layer.name === '.corner_vert') { // type = 11
|
|
84
|
+
// // } else {
|
|
85
|
+
// console.log('unknown fdata', layer)
|
|
86
|
+
// // }
|
|
87
|
+
// }
|
|
88
|
+
// }
|
|
89
|
+
// if (meshData.edata && meshData.edata.layers) {
|
|
90
|
+
// for (let i = 0; i < meshData.edata.totlayer; i++) {
|
|
91
|
+
// const layer = getLayer(meshData.edata.layers, i)
|
|
92
|
+
// // if (layer.type === 0) {
|
|
93
|
+
// // if (layer.name === '.corner_vert') { // type = 11
|
|
94
|
+
// // } else {
|
|
95
|
+
// console.log('unknown edata', layer)
|
|
96
|
+
// // }
|
|
97
|
+
// }
|
|
98
|
+
// }
|
|
99
|
+
|
|
100
|
+
let faceIndices: number[] = []
|
|
101
|
+
if (meshData.poly_offset_indices && meshData.totpoly > 0) {
|
|
102
|
+
faceIndices = [...new Int32Array(meshData.poly_offset_indices.__blender_file__.AB, meshData.poly_offset_indices.__data_address__, meshData.totpoly + 1)]
|
|
103
|
+
}
|
|
104
|
+
// if (faceSize !== 3 && faceSize !== 4 && !faceIndices.length) {
|
|
105
|
+
// console.error('not supported polygons with ', faceSize, 'vertices')
|
|
106
|
+
// return geometry
|
|
107
|
+
// }
|
|
108
|
+
|
|
109
|
+
if (vertices?.data && vertices.data.length > 0) {
|
|
110
|
+
const positions = new Float32Array(vertices.data.length * 3)
|
|
111
|
+
for (let j = 0; j < vertices.data.length; j++) {
|
|
112
|
+
const vertex = vertices.data[j]
|
|
113
|
+
if (vertex.x !== undefined) {
|
|
114
|
+
positions[j * 3] = vertex.x
|
|
115
|
+
positions[j * 3 + 1] = vertex.z
|
|
116
|
+
positions[j * 3 + 2] = -vertex.y
|
|
117
|
+
} else if (vertex.co !== undefined) {
|
|
118
|
+
positions[j * 3] = vertex.co[0]
|
|
119
|
+
positions[j * 3 + 1] = vertex.co[2]
|
|
120
|
+
positions[j * 3 + 2] = -vertex.co[1]
|
|
121
|
+
} else {
|
|
122
|
+
// console.log(bakeGetters(meshData))
|
|
123
|
+
// const t = [...new Int32Array(vertex.__blender_file__.AB, vertex.__data_address__, 8)]
|
|
124
|
+
// debugger
|
|
125
|
+
console.error('BlendLoader - unknown vertex', vertex)
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
geometry.setAttribute('position', new ctx.BufferAttribute(positions, 3))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (indices?.data && indices.data.length > 0 && vertices?.data?.length) {
|
|
133
|
+
const faceSize = meshData.totloop / meshData.totpoly
|
|
134
|
+
if (faceIndices.length > 0) {
|
|
135
|
+
// Use face offset indices for variable-sized faces
|
|
136
|
+
let totalTriangles = 0
|
|
137
|
+
for (let i = 0; i < faceIndices.length - 1; i++) {
|
|
138
|
+
const faceVertCount = faceIndices[i + 1] - faceIndices[i]
|
|
139
|
+
totalTriangles += Math.max(0, faceVertCount - 2)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const indexes = new Uint32Array(totalTriangles * 3)
|
|
143
|
+
let t = 0
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < faceIndices.length - 1; i++) {
|
|
146
|
+
const faceStart = faceIndices[i]
|
|
147
|
+
const faceEnd = faceIndices[i + 1]
|
|
148
|
+
const faceVertCount = faceEnd - faceStart
|
|
149
|
+
|
|
150
|
+
if (faceVertCount >= 3) {
|
|
151
|
+
// todo better Triangulate the face using fan triangulation
|
|
152
|
+
const firstVert = indices.data[faceStart].i
|
|
153
|
+
for (let k = 1; k < faceVertCount - 1; k++) {
|
|
154
|
+
indexes[t++] = firstVert
|
|
155
|
+
indexes[t++] = indices.data[faceStart + k].i
|
|
156
|
+
indexes[t++] = indices.data[faceStart + k + 1].i
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
// debugger
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// console.log(indexes)
|
|
164
|
+
geometry.setIndex(new ctx.BufferAttribute(indexes, 1))
|
|
165
|
+
} else if (faceSize === 3 || faceSize === 4) {
|
|
166
|
+
// Fall back to uniform face size approach
|
|
167
|
+
const isQuad = faceSize === 4
|
|
168
|
+
const faceCount = indices.length / faceSize
|
|
169
|
+
const indexes = new Uint32Array(faceCount * 3 * (isQuad ? 2 : 1))
|
|
170
|
+
|
|
171
|
+
if (faceSize !== 3 && faceSize !== 4) return geometry
|
|
172
|
+
for (let j = 0, t = 0; j < indices.length; j += faceSize) {
|
|
173
|
+
const a = indices[j].i
|
|
174
|
+
const b = indices[j + 1].i
|
|
175
|
+
const c = indices[j + 2].i
|
|
176
|
+
indexes[t++] = a
|
|
177
|
+
indexes[t++] = b
|
|
178
|
+
indexes[t++] = c
|
|
179
|
+
|
|
180
|
+
if (isQuad) {
|
|
181
|
+
const d = indices[j + 3].i
|
|
182
|
+
indexes[t++] = a
|
|
183
|
+
indexes[t++] = c
|
|
184
|
+
indexes[t++] = d
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
geometry.setIndex(new ctx.BufferAttribute(indexes, 1))
|
|
188
|
+
}
|
|
189
|
+
} else if (faceIndices) {
|
|
190
|
+
console.error('BlendLoader - no indices data found, but face indices are present', faceIndices)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// console.log(geometry.attributes.position)
|
|
194
|
+
// console.log(geometry.index)
|
|
195
|
+
|
|
196
|
+
// compute stuff not present
|
|
197
|
+
if (geometry.attributes.position && !geometry.attributes.normal)
|
|
198
|
+
geometry.computeVertexNormals()
|
|
199
|
+
|
|
200
|
+
// if (meshData.loc) { // maybe this is the bbox center?
|
|
201
|
+
// geometry.translate(meshData.loc[0], meshData.loc[2], -meshData.loc[1])
|
|
202
|
+
// }
|
|
203
|
+
// if (meshData.size) { // maybe this is the bbox size?
|
|
204
|
+
// geometry.scale(meshData.size[0], meshData.size[2], -meshData.size[1])
|
|
205
|
+
// }
|
|
206
|
+
|
|
207
|
+
return geometry
|
|
208
|
+
}
|
|
209
|
+
// // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L19
|
|
210
|
+
// // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L1575
|
|
211
|
+
// // todo __set_morph_locs_attribute
|
|
212
|
+
// // todo __set_morph_tangent_attribute
|
|
213
|
+
// // todo pdata(points), edata(edge), fdata(face? legacy?)
|
|
214
|
+
// // meshData.eData = domain edge - https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L189
|
|
215
|
+
// // todo normals, tangents
|
|
216
|
+
|
|
217
|
+
export function createBufferGeometryOld(mesh: any, ctx: Ctx) {
|
|
4
218
|
const
|
|
5
219
|
faces = Array.isArray(mesh.mpoly) ? mesh.mpoly as any[] : [mesh.mpoly],
|
|
6
220
|
loops = mesh.mloop,
|
|
7
221
|
uv = mesh.mloopuv,
|
|
8
222
|
vertices = mesh.mvert
|
|
9
223
|
|
|
10
|
-
const geometry = new BufferGeometry()
|
|
224
|
+
const geometry = new ctx.BufferGeometry()
|
|
11
225
|
|
|
12
226
|
if (!faces) return geometry
|
|
13
227
|
|
|
@@ -18,6 +232,7 @@ export function createBufferGeometry(mesh: any, origin: Vector3Tuple) {
|
|
|
18
232
|
const positions = new Float32Array(size * 3)
|
|
19
233
|
|
|
20
234
|
let currentIndex = 0
|
|
235
|
+
let computeNormals = false
|
|
21
236
|
|
|
22
237
|
for (const face of faces) {
|
|
23
238
|
const len = face.totloop
|
|
@@ -39,13 +254,17 @@ export function createBufferGeometry(mesh: any, origin: Vector3Tuple) {
|
|
|
39
254
|
const vert = vertices[loop.v]
|
|
40
255
|
indices[currentIndex] = currentIndex
|
|
41
256
|
|
|
42
|
-
positions[currentIndex * 3 + 0] = vert.co[0]
|
|
43
|
-
positions[currentIndex * 3 + 1] = vert.co[2]
|
|
44
|
-
positions[currentIndex * 3 + 2] = -vert.co[1]
|
|
257
|
+
positions[currentIndex * 3 + 0] = vert.co[0]
|
|
258
|
+
positions[currentIndex * 3 + 1] = vert.co[2]
|
|
259
|
+
positions[currentIndex * 3 + 2] = -vert.co[1]
|
|
45
260
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
261
|
+
if (vert.no) {
|
|
262
|
+
normals[currentIndex * 3 + 0] = vert.no[0]
|
|
263
|
+
normals[currentIndex * 3 + 1] = vert.no[2]
|
|
264
|
+
normals[currentIndex * 3 + 2] = -vert.no[1]
|
|
265
|
+
} else {
|
|
266
|
+
computeNormals = true
|
|
267
|
+
}
|
|
49
268
|
|
|
50
269
|
if (uv) {
|
|
51
270
|
uvs[currentIndex * 2 + 0] = uv[index].uv[0]
|
|
@@ -59,10 +278,14 @@ export function createBufferGeometry(mesh: any, origin: Vector3Tuple) {
|
|
|
59
278
|
}
|
|
60
279
|
}
|
|
61
280
|
|
|
62
|
-
geometry.setAttribute('position', new BufferAttribute(positions, 3))
|
|
63
|
-
geometry.setIndex(new BufferAttribute(indices, 1))
|
|
64
|
-
geometry.setAttribute('normal', new BufferAttribute(normals, 3))
|
|
65
|
-
geometry.setAttribute('uv', new BufferAttribute(uvs, 2))
|
|
281
|
+
geometry.setAttribute('position', new ctx.BufferAttribute(positions, 3))
|
|
282
|
+
geometry.setIndex(new ctx.BufferAttribute(indices, 1))
|
|
283
|
+
geometry.setAttribute('normal', new ctx.BufferAttribute(normals, 3))
|
|
284
|
+
geometry.setAttribute('uv', new ctx.BufferAttribute(uvs, 2))
|
|
285
|
+
|
|
286
|
+
if (computeNormals) {
|
|
287
|
+
geometry.computeVertexNormals()
|
|
288
|
+
}
|
|
66
289
|
|
|
67
290
|
return geometry
|
|
68
291
|
}
|
package/src/loader/index.ts
CHANGED
|
@@ -1,26 +1,115 @@
|
|
|
1
|
+
import {createCamera} from './camera'
|
|
1
2
|
import {createLight} from './light'
|
|
2
3
|
import {createMesh} from './mesh'
|
|
3
|
-
import {Object3D} from 'threepipe'
|
|
4
|
+
import {IObject3D, Object3D} from 'threepipe'
|
|
5
|
+
import {Ctx} from './ctx'
|
|
6
|
+
import {Euler, EulerOrder} from 'three/src/math/Euler'
|
|
7
|
+
import {Quaternion, Vector3} from 'three'
|
|
4
8
|
|
|
9
|
+
// https://github.com/blender/blender/blob/99a4c93081cc2310a09a14f20a1493e8974c10d2/source/blender/makesdna/DNA_object_types.h#L445
|
|
5
10
|
const blenderObjectTypes = {
|
|
11
|
+
empty: 0,
|
|
6
12
|
mesh: 1,
|
|
7
13
|
lamp: 10,
|
|
14
|
+
camera: 11,
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
export async function createObjects(file: any) {
|
|
17
|
+
export async function createObjects(file: any, ctx: Ctx) {
|
|
11
18
|
const objects: (Object3D|null)[] = []
|
|
12
|
-
const
|
|
19
|
+
const childMap = new Map<any, Object3D[]>()
|
|
20
|
+
const objMap = new Map<any, Object3D>()
|
|
21
|
+
const blendObjects = file.objects.Object ?? []
|
|
22
|
+
const loaded = new WeakMap()
|
|
23
|
+
// console.log(bakeGetters(file))
|
|
13
24
|
for (const object of blendObjects) {
|
|
25
|
+
if (loaded.has(object)) {
|
|
26
|
+
console.warn('BlendLoader - duplicate object')
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
let obj: Object3D | undefined = undefined
|
|
14
30
|
switch (object.type) {
|
|
31
|
+
case blenderObjectTypes.empty:
|
|
32
|
+
obj = new ctx.Object3D()
|
|
33
|
+
break
|
|
15
34
|
case blenderObjectTypes.mesh:
|
|
16
|
-
|
|
35
|
+
obj = createMesh(object, loaded, ctx)
|
|
17
36
|
break
|
|
18
37
|
case blenderObjectTypes.lamp:
|
|
19
|
-
|
|
38
|
+
obj = createLight(object, ctx)
|
|
39
|
+
break
|
|
40
|
+
case blenderObjectTypes.camera:
|
|
41
|
+
obj = createCamera(object, ctx)
|
|
20
42
|
break
|
|
21
43
|
default:
|
|
22
|
-
|
|
44
|
+
obj = new ctx.Object3D()
|
|
45
|
+
console.warn('Unsupported object type', object.type, object, obj)
|
|
46
|
+
}
|
|
47
|
+
if (obj) {
|
|
48
|
+
setTransform(object, obj as any)
|
|
49
|
+
const exChildren = childMap.get(object)
|
|
50
|
+
if (exChildren) {
|
|
51
|
+
for (const exChild of exChildren) {
|
|
52
|
+
obj.add(exChild)
|
|
53
|
+
}
|
|
54
|
+
childMap.delete(object)
|
|
55
|
+
}
|
|
56
|
+
objMap.set(object, obj)
|
|
57
|
+
if (object.parent === object) {
|
|
58
|
+
console.error('BlendLoader - invalid, parent same as object, adding to root', object)
|
|
59
|
+
objects.push(obj)
|
|
60
|
+
} else if (!object.parent) { // just in case
|
|
61
|
+
objects.push(obj)
|
|
62
|
+
} else {
|
|
63
|
+
const parent = objMap.get(object.parent)
|
|
64
|
+
if (!parent) {
|
|
65
|
+
const chi = childMap.get(object.parent) ?? []
|
|
66
|
+
chi.push(obj)
|
|
67
|
+
childMap.set(object, chi)
|
|
68
|
+
} else {
|
|
69
|
+
parent.add(obj)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
loaded.set(object, obj)
|
|
23
73
|
}
|
|
24
74
|
}
|
|
75
|
+
if (childMap.size) {
|
|
76
|
+
const vals = [...childMap.values()].flat()
|
|
77
|
+
console.warn('BlendLoader - unknown objects with parents, adding to root', vals, childMap)
|
|
78
|
+
objects.push(...vals)
|
|
79
|
+
}
|
|
25
80
|
return objects.filter(o => !!o) as Object3D[]
|
|
26
81
|
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
// https://github.com/blender/blender/blob/458e224587e8c45da20841a283cc1b41adc98950/source/blender/makesdna/DNA_action_types.h#L526
|
|
85
|
+
const eulerModes: Record<number, EulerOrder> = {
|
|
86
|
+
[1]: 'XYZ',
|
|
87
|
+
[2]: 'XZY',
|
|
88
|
+
[3]: 'YXZ',
|
|
89
|
+
[4]: 'YZX',
|
|
90
|
+
[5]: 'ZXY',
|
|
91
|
+
[6]: 'ZYX',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setTransform(object: any, obj: IObject3D) {
|
|
95
|
+
obj.name = object.aname
|
|
96
|
+
obj.scale.set(object.size[0], object.size[2], object.size[1])
|
|
97
|
+
obj.position.set(object.loc[0], object.loc[2], -object.loc[1])
|
|
98
|
+
|
|
99
|
+
if (!object.rotmode || object.rotmode === 0) {
|
|
100
|
+
// const quat = new Quaternion()
|
|
101
|
+
obj.quaternion.set(object.quat[1], object.quat[3], -object.quat[2], object.quat[0]) // wxyz
|
|
102
|
+
} else {
|
|
103
|
+
// console.log(object.rotmode)
|
|
104
|
+
// const order = eulerModes[4]
|
|
105
|
+
const order = eulerModes[object.rotmode] ?? 'XYZ'
|
|
106
|
+
const quat = new Quaternion().setFromEuler(new Euler(object.rot[0], object.rot[1], object.rot[2], order.split('').reverse().join('') as any))
|
|
107
|
+
obj.quaternion.set(quat.x, quat.z, -quat.y, quat.w) // wxyz
|
|
108
|
+
}
|
|
109
|
+
// because camera is rotated 90d in blender
|
|
110
|
+
if (object.type === blenderObjectTypes.camera)
|
|
111
|
+
obj.quaternion.multiply(new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2))
|
|
112
|
+
|
|
113
|
+
obj.updateMatrix()
|
|
114
|
+
if (obj.setDirty) obj.setDirty()
|
|
115
|
+
}
|
package/src/loader/light.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Light} from 'threepipe'
|
|
2
|
+
import {Ctx} from './ctx'
|
|
2
3
|
|
|
3
4
|
const blenderLightTypes = {
|
|
4
5
|
point: 0,
|
|
@@ -8,7 +9,7 @@ const blenderLightTypes = {
|
|
|
8
9
|
area: 0,
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export function createLight(lamp: any) {
|
|
12
|
+
export function createLight(lamp: any, ctx: Ctx) {
|
|
12
13
|
const ldata = lamp.data
|
|
13
14
|
|
|
14
15
|
const position = [lamp.loc[0], lamp.loc[2], -lamp.loc[1]]
|
|
@@ -17,22 +18,26 @@ export function createLight(lamp: any) {
|
|
|
17
18
|
const intensity = ldata.energy
|
|
18
19
|
const distance = 0
|
|
19
20
|
|
|
20
|
-
let light =
|
|
21
|
+
let light: Light | undefined = undefined
|
|
21
22
|
|
|
22
23
|
switch (ldata.type) {
|
|
23
24
|
case blenderLightTypes.point:
|
|
24
|
-
light = new PointLight(color, intensity, distance)
|
|
25
|
-
light
|
|
26
|
-
light
|
|
25
|
+
light = new ctx.PointLight(color, intensity, distance)
|
|
26
|
+
light!.position.fromArray(position, 0)
|
|
27
|
+
light!.castShadow = true
|
|
27
28
|
break
|
|
28
29
|
case blenderLightTypes.sun:
|
|
29
|
-
light = new PointLight(color, intensity, distance)
|
|
30
|
-
light
|
|
31
|
-
light
|
|
32
|
-
light
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
light = new ctx.PointLight(color, intensity, distance)
|
|
31
|
+
light!.position.fromArray(position, 0)
|
|
32
|
+
light!.castShadow = true
|
|
33
|
+
if (light!.shadow) {
|
|
34
|
+
light!.shadow.mapSize.width = 1024
|
|
35
|
+
light!.shadow.mapSize.height = 1024
|
|
36
|
+
// @ts-expect-error ??/
|
|
37
|
+
light!.shadow.camera.near = 0.01
|
|
38
|
+
// @ts-expect-error ??/
|
|
39
|
+
light!.shadow.camera.far = 500
|
|
40
|
+
}
|
|
36
41
|
break
|
|
37
42
|
default:
|
|
38
43
|
console.warn('Unsupported light type', ldata.type)
|
package/src/loader/material.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {MeshPhysicalMaterial} from 'threepipe'
|
|
2
|
-
|
|
3
1
|
// todo see blender gltf exporter and convert to js. structure is the same
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
// https://github.com/KhronosGroup/glTF-Blender-IO/blob/ed5100ab6c40472b7c3254fddfe0dd0d76d60644/addons/io_scene_gltf2/blender/exp/material/materials.py#L60
|
|
3
|
+
import {Ctx} from './ctx'
|
|
4
|
+
|
|
5
|
+
export function createMaterial(mat: any, ctx: Ctx) {
|
|
6
|
+
const material = new ctx.MeshPhysicalMaterial()
|
|
6
7
|
material.color.setRGB(mat.r, mat.g, mat.b)
|
|
7
8
|
material.roughness = mat.roughness !== undefined ? mat.roughness : 0.4
|
|
8
9
|
material.metalness = mat.metallic !== undefined ? mat.metallic : 0.0
|
|
9
|
-
material.opacity = mat.alpha !== undefined ? mat.alpha : 0.0
|
|
10
|
+
// material.opacity = mat.alpha !== undefined ? mat.alpha : 0.0
|
|
11
|
+
material.opacity = 1
|
|
10
12
|
material.transparent = material.opacity < 1.0
|
|
11
13
|
return material
|
|
12
14
|
}
|
package/src/loader/mesh.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import {Mesh} from 'threepipe'
|
|
2
1
|
import {createBufferGeometry} from './geometry'
|
|
3
2
|
import {createMaterial} from './material'
|
|
3
|
+
import {Ctx} from './ctx'
|
|
4
4
|
|
|
5
|
-
export function createMesh(object: any) {
|
|
6
|
-
if (!object.data)
|
|
5
|
+
export function createMesh(object: any, loaded: WeakMap<any, any>, ctx: Ctx) {
|
|
6
|
+
if (!object.data) {
|
|
7
|
+
return undefined
|
|
8
|
+
}
|
|
7
9
|
|
|
8
|
-
const geometry =
|
|
10
|
+
const geometry = loaded.get(object.data) ?? createBufferGeometry(object.data, ctx)
|
|
11
|
+
loaded.set(object.data, geometry)
|
|
9
12
|
|
|
10
13
|
const mat = object.data.mat[0]
|
|
11
14
|
|
|
12
|
-
const material = mat ? createMaterial(mat) : undefined
|
|
13
|
-
const
|
|
15
|
+
// const material = mat ? createMaterial(mat) : undefined
|
|
16
|
+
const material = mat ? loaded.get(mat) ?? createMaterial(mat, ctx) : new ctx.MeshPhysicalMaterial()
|
|
17
|
+
if (mat) loaded.set(mat, material)
|
|
18
|
+
|
|
19
|
+
// console.log(material, mat)
|
|
20
|
+
// material.side = DoubleSide
|
|
21
|
+
const mesh = new ctx.Mesh(geometry, material)
|
|
14
22
|
|
|
15
23
|
mesh.castShadow = true
|
|
16
24
|
mesh.receiveShadow = true
|
|
17
25
|
|
|
18
|
-
mesh.rotateZ(object.rot[2])
|
|
19
|
-
mesh.rotateY(object.rot[1])
|
|
20
|
-
mesh.rotateX(object.rot[0])
|
|
21
|
-
|
|
22
|
-
mesh.scale.fromArray(object.size, 0)
|
|
23
|
-
mesh.position.fromArray([object.loc[0], object.loc[2], -object.loc[1]], 0)
|
|
24
|
-
|
|
25
26
|
return mesh
|
|
26
27
|
}
|