This commit is contained in:
Robert Long 2018-10-17 14:25:31 -07:00
Родитель efda58eb00
Коммит 8ba71a83fd
7 изменённых файлов: 96 добавлений и 59 удалений

Просмотреть файл

@ -9,7 +9,7 @@ module.exports = {
rules: {
"prettier/prettier": "error",
"prefer-const": "error",
"no-use-before-define": ["error", { "functions": false, "classes": false, "variables": true }],
"no-use-before-define": ["error", { functions: false, classes: false, variables: true }],
"no-var": "error",
"no-throw-literal": "error",
// Light console usage is useful but remove debug logs before merging to master.
@ -17,16 +17,17 @@ module.exports = {
"lines-between-class-members": 2,
"padding-line-between-statements": [
"error",
{ "blankLine": "always", "prev": "function", "next": "function" },
{ "blankLine": "always", "prev": "function", "next": "class" },
{ "blankLine": "always", "prev": "class", "next": "function" },
{ "blankLine": "always", "prev": "class", "next": "export" },
{ "blankLine": "always", "prev": "export", "next": "function" },
{ "blankLine": "always", "prev": "export", "next": "class" },
{ "blankLine": "always", "prev": "export", "next": "export" },
{ "blankLine": "always", "prev": "import", "next": "function" },
{ "blankLine": "always", "prev": "import", "next": "class" },
]
{ blankLine: "always", prev: "function", next: "function" },
{ blankLine: "always", prev: "function", next: "class" },
{ blankLine: "always", prev: "class", next: "function" },
{ blankLine: "always", prev: "class", next: "export" },
{ blankLine: "always", prev: "export", next: "function" },
{ blankLine: "always", prev: "export", next: "class" },
{ blankLine: "always", prev: "export", next: "export" },
{ blankLine: "always", prev: "import", next: "function" },
{ blankLine: "always", prev: "import", next: "class" }
],
"no-unused-vars": ["error", { varsIgnorePattern: "^_", argsIgnorePattern: "^_", ignoreRestSiblings: true }]
},
extends: ["prettier", "plugin:react/recommended", "eslint:recommended"]
};

Просмотреть файл

@ -943,7 +943,7 @@ export default class Editor {
}
});
MeshCombinationGroup.combineMeshes(clonedScene);
await MeshCombinationGroup.combineMeshes(clonedScene);
const animations = [];

Просмотреть файл

@ -1,41 +1,40 @@
import THREE from "./three";
import { isStatic, computeAndSetStaticModes } from "./StaticMode";
import asyncTraverse from "./utils/asyncTraverse";
import keysEqual from "./utils/keysEqual";
import hashImage from "./utils/getImageData";
function getBase64Image(img) {
// Create an empty canvas element
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
export async function getImageHash(hashCache, img) {
let hash = hashCache.get(img);
// Copy the image contents to the canvas
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
const dataURL = canvas.toDataURL("image/png");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
export function compareImages(a, b) {
if (a && b) {
return a.src === b.src || getBase64Image(a) === getBase64Image(b);
if (!hash) {
hash = await hashImage(img);
hashCache.set(img, hash);
}
return a === b;
return hash;
}
export function compareTextures(a, b) {
export async function compareImages(hashCache, a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
return (await getImageHash(hashCache, a)) === (await getImageHash(hashCache, b));
}
export async function compareTextures(hashCache, a, b) {
if (a && b) {
return (
compareImages(a, b) &&
a.wrapS === b.wrapS &&
a.wrapT === b.wrapT &&
a.magFilter === b.magFilter &&
a.minFilter === b.minFilter
a.minFilter === b.minFilter &&
(await compareImages(hashCache, a.image, b.image))
);
}
@ -44,7 +43,9 @@ export function compareTextures(a, b) {
export default class MeshCombinationGroup {
static MaterialComparators = {
MeshStandardMaterial: (a, b) => {
MeshStandardMaterial: async (group, a, b) => {
const imageHashes = group.imageHashes;
return (
a.opacity === b.opacity &&
a.roughness === b.roughness &&
@ -53,27 +54,27 @@ export default class MeshCombinationGroup {
a.normalScale.equals(b.normalScale) &&
a.color.equals(b.color) &&
a.emissive.equals(b.emissive) &&
compareTextures(a.map, b.map) &&
compareTextures(a.roughnessMap, b.roughnessMap) &&
compareTextures(a.metalnessMap, b.metalnessMap) &&
compareTextures(a.aoMap, b.aoMap) &&
compareTextures(a.normalMap, b.normalMap) &&
compareTextures(a.emissiveMap, b.emissiveMap)
(await compareTextures(imageHashes, a.map, b.map)) &&
(await compareTextures(imageHashes, a.roughnessMap, b.roughnessMap)) &&
(await compareTextures(imageHashes, a.metalnessMap, b.metalnessMap)) &&
(await compareTextures(imageHashes, a.aoMap, b.aoMap)) &&
(await compareTextures(imageHashes, a.normalMap, b.normalMap)) &&
(await compareTextures(imageHashes, a.emissiveMap, b.emissiveMap))
);
}
};
static combineMeshes(rootObject) {
static async combineMeshes(rootObject) {
computeAndSetStaticModes(rootObject);
const meshCombinationGroups = [];
rootObject.traverse(object => {
await asyncTraverse(rootObject, async object => {
if (isStatic(object) && object.isMesh) {
let added = false;
for (const group of meshCombinationGroups) {
if (group.tryAdd(object)) {
if (await group._tryAdd(object)) {
added = true;
break;
}
@ -86,7 +87,7 @@ export default class MeshCombinationGroup {
});
for (const group of meshCombinationGroups) {
const combinedMesh = group.combine();
const combinedMesh = group._combine();
if (combinedMesh) {
rootObject.add(combinedMesh);
@ -103,9 +104,10 @@ export default class MeshCombinationGroup {
this.initialObject = initialObject;
this.meshes = [initialObject];
this.imageHashes = new Map();
}
tryAdd(object) {
async _tryAdd(object) {
if (!object.isMesh) {
return false;
}
@ -116,18 +118,11 @@ export default class MeshCombinationGroup {
const compareMaterial = MeshCombinationGroup.MaterialComparators[object.material.type];
if (!(compareMaterial && compareMaterial(this.initialObject.material, object.material))) {
if (!(compareMaterial && (await compareMaterial(this, this.initialObject.material, object.material)))) {
return false;
}
const initialGeometryAttributes = Object.keys(this.initialObject.geometry.attributes);
const curGeometryAttributes = Object.keys(object.geometry.attributes);
if (initialGeometryAttributes.length !== curGeometryAttributes.length) {
return false;
}
if (initialGeometryAttributes.some(attrName => curGeometryAttributes.indexOf(attrName) === -1)) {
if (!keysEqual(this.initialObject.geometry.attributes, object.geometry.attributes)) {
return false;
}
@ -136,7 +131,7 @@ export default class MeshCombinationGroup {
return true;
}
combine() {
_combine() {
const originalMesh = this.meshes[0];
if (this.meshes.length === 1) {

Просмотреть файл

@ -0,0 +1,7 @@
export default async function asyncTraverse(object, callback) {
await callback(object);
for (const child of object.children) {
await asyncTraverse(child, callback);
}
}

Просмотреть файл

@ -0,0 +1,8 @@
export default function getImageData(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, canvas.width, canvas.height);
}

Просмотреть файл

@ -0,0 +1,8 @@
import getImageData from "./getImageData";
export default async function hashImage(img) {
const imageData = getImageData(img);
const digest = await crypto.subtle.digest("SHA-256", imageData.data);
const hashArray = Array.from(new Uint8Array(digest));
return hashArray.map(b => ("00" + b.toString(16)).slice(-2)).join("");
}

Просмотреть файл

@ -0,0 +1,18 @@
export default function keysEqual(a, b) {
let aKeyCount = 0;
let bKeyCount = 0;
for (const key in a) {
aKeyCount++;
if (!b.hasOwnProperty(key)) {
return false;
}
}
for (const _bKey in b) {
bKeyCount++;
}
return aKeyCount === bKeyCount;
}