Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@
},
"workspaces": [
"packages/*"
]
],
"dependencies": {
"three": "^0.181.2"
}
}
Binary file removed packages/example/resources/diamond.png
Binary file not shown.
3 changes: 3 additions & 0 deletions packages/example/resources/fire.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,8 @@
"depthWrite": false,
"alphaTest": 0.01
}
},
"textures": {
"diamond": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAYAAABXuSs3AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAALqADAAQAAAABAAAALgAAAABSkiQEAAADBUlEQVRoBc3Xv4saQRQHcDVB8HexYiEJKCg2KTQpUmqdJnX+Ahsr/xatrKzyH6QLKLE5UiZwiIggQb2QiOJv8Ufey/GOsMy6OzNvIAPesnPum+99bnT3+XyGRrvdthqNhmWovC9gonCz2XwJdd8nEonXJupjTSPB0+n0q2Kx+KFUKr1rtVovTIR/zl0UtQuFQjmZTJY2m42VyWQ6sMYP7nXYxVE7FotV4vG4FYlEcqlUqmJCnTU4acPezqFwMBiMQfgyqL/5r8VJG8Sfvk2i0agRdTZxuzYJm1JnCy7SpvAm1FmCO2lTcBPqLMFvaVN4bnXt4G7aFJxbXTu4F20Kz6muFdyrNgXnVNcKLqNN4bnUlYPLalNwLnXl4CraFJ5DXSm4qjYF51BXCo7aoFaBByjrcrn4RK/r9erDl9PQVZcOjtr5fL4MD1I5UWCauxUa/xhddengqA3SlXA47KhN4Z20aV5HXaoDwoYgm80+aVMA0dFNHK/5R70Dp1JdkpS4ZVl/tUOhkKu22x7H4DhU1aXEj8fjT7/fPzmdTudAIPDscWnxTy/ieOX5fD7A1locDoeduJJ4Vir4ZDK5n8/nXfgXv4WeMi0u+TjrNfh2u/02Go06tVrt96169t9JbZV6vb4bDAY96N7vUJ0+hKKjl+C73e7Xer3uAsh3ezC3c6ngWAwWul8ul93VavUgCkxzXva4qjbmkA5erVa3w+GwB1o31d3EdbSVguNFqL5YLFzV8b1OQ0cba0qL40WoDh+o3n6/d1S/Ja6rrRwcLyR1OAr3+q09rqutFRzVx+Ox4153EufQ1gpO6iDeBcEHPPcyOLRxHaU9TgFJHe56d/A1eKZ5pyOXNtbXCo4FcK97VefSZgnuVZ1TmyW4V3VObbbgburc2mzB3dS5tVmDO6mb0GYN7qRuQps9uF3dlDZ7cLs6NBxK3Q3WcRvaNyD7AqQ+nU4/QcPxWaW7sdcUnUv1nKICojm8m/b7/Y/QCO9ke0lRPdEcuzgugurw7PJlNpt9FS3KMfcHixI9jZd3/ggAAAAASUVORK5CYII="
}
}
6 changes: 0 additions & 6 deletions packages/example/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,6 @@ function onResize() {
let particleEffect: ParticleEffect | null = null
const loader = new ParticleEffectLoader()

// Bind the "diamond" texture key used in fire.json to the local diamond.png file.
const textureLoader = new TextureLoader()
loader.setTextures({
diamond: textureLoader.load('./diamond.png'),
})

loader
.loadAsync('./fire.json')
.then((model) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/three-particles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"globals": "^16.0.0"
},
"peerDependencies": {
"three": ">=0.180.0 <0.181.0"
"three": ">=0.181.2 <0.182.0"
},
"dependencies": {
"@types/lodash": "^4.17.20",
Expand Down
8 changes: 4 additions & 4 deletions packages/three-particles/src/ParticleEffectLoader.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import {
BufferGeometry,
BufferGeometryLoader,
FileLoader,
Loader,
Material,
MaterialLoader,
Texture,
TextureLoader,
BufferGeometry,
} from 'three'
import { parseTextureJson } from './parseTextureJson'
import {
ParticleEffectModelJson,
parseParticleEffect,
ParticleEffectModel,
ParticleEffectModelJson,
} from './model'
import { LoadingManager } from 'three/src/loaders/LoadingManager'
import { decodeText } from './util'
import { cloneDeep } from 'lodash'
import { getDefaultRadial } from './materialDefaults'
import { ReadonlyDeep } from 'type-fest'

/**
* Loads a JSON file describing a particle effect.
Expand Down Expand Up @@ -112,7 +113,7 @@ export class ParticleEffectLoader extends Loader<ParticleEffectModel> {
* @return {ParticleEffect} The parsed ParticleEffect object.
*/
async parseAsync(
json: ParticleEffectModelJson,
json: ReadonlyDeep<ParticleEffectModelJson>,
): Promise<ParticleEffectModel> {
json = cloneDeep(json)

Expand Down Expand Up @@ -163,7 +164,6 @@ export class ParticleEffectLoader extends Loader<ParticleEffectModel> {
bundledMaterials: bundledMaterials,
externalMaterials: this.materials,
bundledTextures: bundledTextures,
externalTextures: this.textures,
bundledGeometries: bundledGeometries,
externalGeometries: this.geometries,
})
Expand Down
127 changes: 7 additions & 120 deletions packages/three-particles/src/model/ParticleEffectModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {
parseEmitter,
ParticleEmitterModel,
ParticleEmitterModelJson,
particleEmitterModelToJson,
} from './ParticleEmitterModel'
import { PartialDeep } from 'type-fest'
import { PartialDeep, ReadonlyDeep } from 'type-fest'
import {
BufferGeometry,
BufferGeometryJSON,
Expand Down Expand Up @@ -40,6 +39,8 @@ export interface EmitterDurationModel {
delayAfter: RangeModel
}

export type EmitterDurationModelJson = PartialDeep<EmitterDurationModel>

/**
* Parameters for creating a new particle effect.
*/
Expand All @@ -49,7 +50,6 @@ export interface ParticleEffectModel {
materials: Record<string, Material>
textures: Record<string, Texture>
geometries: Record<string, BufferGeometry>
toJSON(): ParticleEffectModelJson
}

/**
Expand All @@ -61,8 +61,8 @@ export const particleEffectDefaults = {
} as const

export type ParticleEffectModelJson = Omit<
PartialDeep<ParticleEffectModel, { recurseIntoArrays: true }>,
'emitters' | 'materials' | 'geometries'
PartialDeep<ParticleEffectModel>,
'emitters' | 'materials' | 'geometries' | 'textures'
> & {
emitters?: ParticleEmitterModelJson[]

Expand All @@ -78,7 +78,7 @@ export type ParticleEffectModelJson = Omit<
* Allow either TextureJSON blobs or string URLs.
* External textures can be set on the ParticleEffectLoader.
*/
textures?: Record<string, TextureJSON | string>
textures?: Record<string, Partial<TextureJSON> | string>

/**
* Optional geometries bundled with this effect.
Expand All @@ -97,15 +97,13 @@ export function parseParticleEffect({
bundledMaterials,
externalMaterials,
bundledTextures,
externalTextures,
bundledGeometries,
externalGeometries,
}: {
effectJson: ParticleEffectModelJson
effectJson: ReadonlyDeep<ParticleEffectModelJson>
bundledMaterials: Record<string, Material>
externalMaterials: Record<string, Material>
bundledTextures: Record<string, Texture>
externalTextures: Record<string, Texture>
bundledGeometries: Record<string, BufferGeometry>
externalGeometries: Record<string, BufferGeometry>
}): ParticleEffectModel {
Expand All @@ -126,122 +124,11 @@ export function parseParticleEffect({
geometries: allGeometries,
}),
)

return {
version: effectJson.version ?? particleEffectDefaults.version,
emitters,
materials: bundledMaterials,
textures: bundledTextures,
geometries: bundledGeometries,
toJSON: function (this: ParticleEffectModel) {
return particleEffectModelToJson({
effect: this,
externalMaterials,
externalTextures,
externalGeometries,
})
},
}
}

/**
* Returns a compact representation of a ParticleEffectModel with default values removed.
*/
export function particleEffectModelToJson({
effect,
externalMaterials,
externalTextures,
externalGeometries,
}: {
effect: ParticleEffectModel
externalMaterials: Record<string, Material>
externalTextures: Record<string, Texture>
externalGeometries: Record<string, BufferGeometry>
}): ParticleEffectModelJson {
const out: ParticleEffectModelJson = {}
const allMaterials = {
...externalMaterials,
...effect.materials,
}
const allGeometries = {
...externalGeometries,
...effect.geometries,
}
out.version = effect.version
if (effect.emitters.length)
out.emitters = effect.emitters.map((e) =>
particleEmitterModelToJson(e, allMaterials, allGeometries),
)

const materialEntries = Object.entries(effect.materials)
if (materialEntries.length) {
const materialsJson: Record<string, MaterialJSON> = {}
const textureUuidMap = createTextureUuidMap(externalTextures)
for (const [id, mat] of materialEntries) {
materialsJson[id] = materialToJson(mat, textureUuidMap)
}
if (Object.keys(materialsJson).length) out.materials = materialsJson
}

// Serialize bundled geometries (do not include external geometries)
const geometryEntries = Object.entries(effect.geometries)
if (geometryEntries.length) {
const geometriesJson: Record<string, BufferGeometryJSON> = {}
for (const [id, geom] of geometryEntries) {
// Use three.js BufferGeometry.toJSON to serialize
geometriesJson[id] = geom.toJSON() as unknown as BufferGeometryJSON
}
if (Object.keys(geometriesJson).length) out.geometries = geometriesJson
}

return out
}

function createTextureUuidMap(
textures: Record<string, Texture>,
): Record<string, string> {
// Build a lookup from texture UUID to the provided key so we can map
// serialized texture references (uuids) back to keys used by MaterialLoader.
const uuidToKey: Record<string, string> = {}
for (const [key, tex] of Object.entries(textures)) {
uuidToKey[tex.uuid] = key
}
return uuidToKey
}

/**
* Returns a compact representation of a material with default values removed.
*/
function materialToJson(
material: Material,
textureUuidToKeyMap: Record<string, string>,
): MaterialJSON {
// Use three.js Material.toJSON to serialize material
const matJson: MaterialJSON = material.toJSON()
delete matJson.textures
delete matJson.images
return replaceTextureUuids(matJson, textureUuidToKeyMap)
}

/**
* Replace any texture UUID references with their corresponding keys
*/
function replaceTextureUuids(
val: any,
textureUuidToKeyMap: Record<string, string>,
): any {
if (Array.isArray(val))
return val.map((element) =>
replaceTextureUuids(element, textureUuidToKeyMap),
)
if (val && typeof val === 'object') {
const outObj: any = {}
for (const [k, v] of Object.entries(val)) {
outObj[k] = replaceTextureUuids(v, textureUuidToKeyMap)
}
return outObj
}
if (typeof val === 'string' && textureUuidToKeyMap[val])
return textureUuidToKeyMap[val]
return val
}
Loading