From a6c2f80b4b59ebe8846268d9e681ab37f37125d5 Mon Sep 17 00:00:00 2001 From: MbfloydIR <144718558+MbfloydIR@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:11:22 -0700 Subject: [PATCH] Michael/ir 2056 render unit tests (#10374) * added unit test for fog component * started the unit test for the webglrendersystem * updated the mock engine renderer corrected the webGLRendererSystem test for changing the render mode * added tests for webGL render systems engine render settings reactors * added tests for webgl render systems camera layer reactors * added unity test for webGL render system resize * added test for visible children * updated the web gl render unit ttest * removed dev testing code * implemented PR changes * changed the background color to white added assert check that the background color is white * corrected the npm errors found * removed globalThis._scene ref and use the local scene ref instead * removed performanceState assert per the PR request * removed dev log from portal component Added fog shader asserts to fog settings components test * corrected npm found errors * corrected the npm errors found --------- Co-authored-by: Josh Field --- .../src/renderer/WebGLRendererSystem.test.tsx | 196 ++++++++++++++++++ .../components/FogSettingsComponent.test.tsx | 127 ++++++++++++ .../src/renderer/components/FogShaders.ts | 2 +- .../spatial/tests/util/MockEngineRenderer.ts | 23 +- 4 files changed, 343 insertions(+), 5 deletions(-) create mode 100644 packages/spatial/src/renderer/WebGLRendererSystem.test.tsx create mode 100644 packages/spatial/src/renderer/components/FogSettingsComponent.test.tsx diff --git a/packages/spatial/src/renderer/WebGLRendererSystem.test.tsx b/packages/spatial/src/renderer/WebGLRendererSystem.test.tsx new file mode 100644 index 0000000000..bc9e38cd95 --- /dev/null +++ b/packages/spatial/src/renderer/WebGLRendererSystem.test.tsx @@ -0,0 +1,196 @@ +// /* +// CPAL-1.0 License + +// The contents of this file are subject to the Common Public Attribution License +// Version 1.0. (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +// The License is based on the Mozilla Public License Version 1.1, but Sections 14 +// and 15 have been added to cover use of software over a computer network and +// provide for limited attribution for the Original Developer. In addition, +// Exhibit A has been modified to be consistent with Exhibit B. + +// Software distributed under the License is distributed on an "AS IS" basis, +// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +// specific language governing rights and limitations under the License. + +// The Original Code is Ethereal Engine. + +// The Original Developer is the Initial Developer. The Initial Developer of the +// Original Code is the Ethereal Engine team. + +// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +// Ethereal Engine. All Rights Reserved. +// */ + +import { + Engine, + Entity, + EntityUUID, + SystemDefinitions, + UUIDComponent, + createEntity, + destroyEngine, + getComponent, + getMutableComponent, + setComponent +} from '@etherealengine/ecs' +import { getMutableState } from '@etherealengine/hyperflux' +import { act, render } from '@testing-library/react' +import assert from 'assert' +import { EffectComposer, RenderPass } from 'postprocessing' +import React from 'react' +import { Color, Group, MathUtils, Texture } from 'three' +import { MockEngineRenderer } from '../../tests/util/MockEngineRenderer' +import { CameraComponent } from '../camera/components/CameraComponent' +import { createEngine } from '../initializeEngine' +import { EntityTreeComponent } from '../transform/components/EntityTree' +import { RendererState } from './RendererState' +import { + RendererComponent, + WebGLRendererSystem, + getNestedVisibleChildren, + getSceneParameters +} from './WebGLRendererSystem' +import { FogSettingsComponent, FogType } from './components/FogSettingsComponent' +import { GroupComponent, addObjectToGroup } from './components/GroupComponent' +import { Object3DComponent } from './components/Object3DComponent' +import { BackgroundComponent, EnvironmentMapComponent, SceneComponent } from './components/SceneComponents' +import { VisibleComponent } from './components/VisibleComponent' +import { ObjectLayers } from './constants/ObjectLayers' +import { RenderModes } from './constants/RenderModes' + +describe('WebGl Renderer System', () => { + let rootEntity: Entity + let visibleEntity: Entity + let invisibleEntity: Entity + let nestedVisibleEntity: Entity + let nestedInvisibleEntity: Entity + + const mockCanvas = () => { + return { + getDrawingBufferSize: () => 0, + getContext: () => {}, + parentElement: { + clientWidth: 100, + clientHeight: 100 + } + } as any as HTMLCanvasElement + } + + beforeEach(() => { + createEngine() + + rootEntity = Engine.instance.viewerEntity //createEntity() + setComponent(rootEntity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(rootEntity, EntityTreeComponent) + setComponent(rootEntity, CameraComponent) + setComponent(rootEntity, RendererComponent, { canvas: mockCanvas() }) + getMutableComponent(rootEntity, RendererComponent).set(new MockEngineRenderer()) + const rendererComp = getMutableComponent(rootEntity, RendererComponent) + rendererComp.canvas.set(mockCanvas()) + setComponent(rootEntity, BackgroundComponent, new Color(0xffffff)) + + setComponent(rootEntity, EnvironmentMapComponent, new Texture()) + setComponent(rootEntity, FogSettingsComponent, { type: FogType.Height }) + + invisibleEntity = createEntity() + setComponent(invisibleEntity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(invisibleEntity, GroupComponent) + const invisibleObject3d = setComponent(invisibleEntity, Object3DComponent, new Group()) + addObjectToGroup(invisibleEntity, invisibleObject3d) + setComponent(invisibleEntity, EntityTreeComponent) + + visibleEntity = createEntity() + setComponent(visibleEntity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(visibleEntity, VisibleComponent) + const visibleObject3d = setComponent(visibleEntity, Object3DComponent, new Group()) + addObjectToGroup(visibleEntity, visibleObject3d) + setComponent(visibleEntity, GroupComponent) + setComponent(visibleEntity, EntityTreeComponent) + + setComponent(rootEntity, SceneComponent, { children: [invisibleEntity, visibleEntity] }) + + nestedInvisibleEntity = createEntity() + setComponent(nestedInvisibleEntity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(nestedInvisibleEntity, GroupComponent) + const nestedInvisibleObject3d = setComponent(nestedInvisibleEntity, Object3DComponent, new Group()) + addObjectToGroup(nestedInvisibleEntity, nestedInvisibleObject3d) + setComponent(nestedInvisibleEntity, EntityTreeComponent) + setComponent(visibleEntity, SceneComponent, { children: [nestedInvisibleEntity] }) + + nestedVisibleEntity = createEntity() + setComponent(nestedVisibleEntity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(nestedVisibleEntity, VisibleComponent) + const nestedVisibleObject3d = setComponent(nestedVisibleEntity, Object3DComponent, new Group()) + addObjectToGroup(nestedVisibleEntity, nestedVisibleObject3d) + setComponent(nestedVisibleEntity, GroupComponent) + setComponent(nestedVisibleEntity, EntityTreeComponent) + setComponent(invisibleEntity, SceneComponent, { children: [nestedVisibleEntity] }) + + //override addpass to test data without dependency on Browser + let addPassCount = 0 + EffectComposer.prototype.addPass = () => { + addPassCount++ + } + }) + + afterEach(() => { + return destroyEngine() + }) + + it('Test Background, Environment, Fog Components', async () => { + const { background, environment, fog, children } = getSceneParameters([rootEntity]) + SystemDefinitions.get(WebGLRendererSystem)?.execute() + assert(background, 'background component exists') + const backgroundColor = background as Color + assert( + backgroundColor.r == 1 && backgroundColor.g == 1 && backgroundColor.b == 1, + 'backgroud color was set correctly' + ) + + assert(environment, 'environment component exists') + assert(fog, 'fog component exists') + }) + + it('Test WebGL Reactors', async () => { + const webGLRendererSystem = SystemDefinitions.get(WebGLRendererSystem) + const RenderSystem = webGLRendererSystem?.reactor! + const tag = + const { rerender, unmount } = render(tag) + + SystemDefinitions.get(WebGLRendererSystem)?.execute() + + const engineRendererSettings = getMutableState(RendererState) + engineRendererSettings.renderMode.set(RenderModes.WIREFRAME) + engineRendererSettings.renderScale.set(2) + engineRendererSettings.qualityLevel.set(3) + engineRendererSettings.automatic.set(false) + engineRendererSettings.physicsDebug.set(true) + engineRendererSettings.avatarDebug.set(true) + engineRendererSettings.gridVisibility.set(true) + engineRendererSettings.nodeHelperVisibility.set(true) + + await act(() => rerender(tag)) + + const camera = getComponent(rootEntity, CameraComponent) + const rendererComp = getComponent(rootEntity, RendererComponent) + const effectComposer = rendererComp.effectComposer + const passes = effectComposer?.passes.filter((p) => p.name === 'RenderPass') as any + const renderPass: RenderPass = passes ? passes[0] : undefined + + assert(renderPass.overrideMaterial, 'change render mode') + assert(rendererComp.needsResize, 'change render scale') + assert(camera.layers.isEnabled(ObjectLayers.PhysicsHelper), 'enable physicsDebug') + assert(camera.layers.isEnabled(ObjectLayers.AvatarHelper), 'enable avatarDebug') + assert(camera.layers.isEnabled(ObjectLayers.Gizmos), 'enable gridVisibility') + assert(camera.layers.isEnabled(ObjectLayers.NodeHelper), 'enable nodeHelperVisibility') + + webGLRendererSystem?.execute() + + assert(!rendererComp.needsResize, 'resize updated') + const scene = getComponent(rootEntity, SceneComponent) + const entitiesToRender = scene.children.map(getNestedVisibleChildren).flat() + assert(entitiesToRender.length == 1 && entitiesToRender[0] == visibleEntity, 'visible children') + }) +}) diff --git a/packages/spatial/src/renderer/components/FogSettingsComponent.test.tsx b/packages/spatial/src/renderer/components/FogSettingsComponent.test.tsx new file mode 100644 index 0000000000..d7fd55a85d --- /dev/null +++ b/packages/spatial/src/renderer/components/FogSettingsComponent.test.tsx @@ -0,0 +1,127 @@ +// /* +// CPAL-1.0 License + +// The contents of this file are subject to the Common Public Attribution License +// Version 1.0. (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +// The License is based on the Mozilla Public License Version 1.1, but Sections 14 +// and 15 have been added to cover use of software over a computer network and +// provide for limited attribution for the Original Developer. In addition, +// Exhibit A has been modified to be consistent with Exhibit B. + +// Software distributed under the License is distributed on an "AS IS" basis, +// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +// specific language governing rights and limitations under the License. + +// The Original Code is Ethereal Engine. + +// The Original Developer is the Initial Developer. The Initial Developer of the +// Original Code is the Ethereal Engine team. + +// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +// Ethereal Engine. All Rights Reserved. +// */ + +import { + Entity, + EntityUUID, + UUIDComponent, + createEntity, + destroyEngine, + getComponent, + getMutableComponent, + setComponent +} from '@etherealengine/ecs' +import { act, render } from '@testing-library/react' +import assert from 'assert' +import React from 'react' +import { Fog, FogExp2, MathUtils, ShaderChunk } from 'three' +import { CameraComponent } from '../../camera/components/CameraComponent' +import { createEngine } from '../../initializeEngine' +import { EntityTreeComponent } from '../../transform/components/EntityTree' +import { RendererComponent } from '../WebGLRendererSystem' +import { FogSettingsComponent, FogType } from './FogSettingsComponent' +import { FogShaders } from './FogShaders' +import { FogComponent, SceneComponent } from './SceneComponents' + +describe('FogSettingsComponent', () => { + let rootEntity: Entity + let entity: Entity + + const mockCanvas = () => { + return { + getDrawingBufferSize: () => 0 + } as any as HTMLCanvasElement + } + + beforeEach(() => { + createEngine() + + rootEntity = createEntity() + setComponent(rootEntity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(rootEntity, EntityTreeComponent) + setComponent(rootEntity, CameraComponent) + setComponent(rootEntity, SceneComponent) + setComponent(rootEntity, RendererComponent, { canvas: mockCanvas() }) + + entity = createEntity() + setComponent(entity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(entity, FogSettingsComponent) + setComponent(entity, EntityTreeComponent) + + //set data to test + setComponent(rootEntity, SceneComponent, { children: [entity] }) + }) + + afterEach(() => { + return destroyEngine() + }) + + it('Create Fog Setting Component', async () => { + const fogSettingsComponent = getMutableComponent(entity, FogSettingsComponent) + assert(fogSettingsComponent.value, 'fog setting component exists') + + fogSettingsComponent.type.set(FogType.Height) + fogSettingsComponent.color.set('#ff0000') + fogSettingsComponent.far.set(2000) + fogSettingsComponent.near.set(2) + fogSettingsComponent.density.set(0.02) + const { rerender, unmount } = render(<>) + + await act(() => { + rerender(<>) + }) + const fogComponent = getComponent(entity, FogComponent) + assert(fogComponent, 'created fog component') + assert(fogComponent.color.r == 1 && fogComponent.color.g == 0 && fogComponent.color.b == 0, 'fog set color') + const fog = fogComponent as Fog + assert(fog.far == 2000, 'fog set far') + assert(fog.near == 2, 'fog set near') + const fogExp2 = fogComponent as FogExp2 + assert(fogExp2.density == 0.02, 'fog set density') + + assert(ShaderChunk.fog_fragment == FogShaders.fog_fragment.heightFog) + assert(ShaderChunk.fog_pars_fragment == FogShaders.fog_pars_fragment.heightFog) + assert(ShaderChunk.fog_vertex == FogShaders.fog_vertex.heightFog) + assert(ShaderChunk.fog_pars_vertex == FogShaders.fog_pars_vertex.heightFog) + + fogSettingsComponent.type.set(FogType.Linear) + await act(() => { + rerender(<>) + }) + assert(ShaderChunk.fog_fragment == FogShaders.fog_fragment.default) + assert(ShaderChunk.fog_pars_fragment == FogShaders.fog_pars_fragment.default) + assert(ShaderChunk.fog_vertex == FogShaders.fog_vertex.default) + assert(ShaderChunk.fog_pars_vertex == FogShaders.fog_pars_vertex.default) + + fogSettingsComponent.type.set(FogType.Brownian) + await act(() => { + rerender(<>) + }) + assert(ShaderChunk.fog_fragment == FogShaders.fog_fragment.brownianMotionFog) + assert(ShaderChunk.fog_pars_fragment == FogShaders.fog_pars_fragment.brownianMotionFog) + assert(ShaderChunk.fog_vertex == FogShaders.fog_vertex.brownianMotionFog) + assert(ShaderChunk.fog_pars_vertex == FogShaders.fog_pars_vertex.brownianMotionFog) + }) +}) diff --git a/packages/spatial/src/renderer/components/FogShaders.ts b/packages/spatial/src/renderer/components/FogShaders.ts index 0e16ac1a61..e41f02d774 100644 --- a/packages/spatial/src/renderer/components/FogShaders.ts +++ b/packages/spatial/src/renderer/components/FogShaders.ts @@ -98,7 +98,7 @@ float FBM(vec3 p) { } ` -const FogShaders = { +export const FogShaders = { fog_fragment: { default: ShaderChunk.fog_fragment, brownianMotionFog: `#ifdef USE_FOG diff --git a/packages/spatial/tests/util/MockEngineRenderer.ts b/packages/spatial/tests/util/MockEngineRenderer.ts index a6989b1d25..012940569f 100644 --- a/packages/spatial/tests/util/MockEngineRenderer.ts +++ b/packages/spatial/tests/util/MockEngineRenderer.ts @@ -25,17 +25,32 @@ Ethereal Engine. All Rights Reserved. import { WebGLRenderer } from 'three/src/renderers/WebGLRenderer' +import { EffectComposer } from 'postprocessing' import { EngineRenderer } from '../../src/renderer/WebGLRendererSystem' import { MockEventListener } from './MockEventListener' +class MockRenderer { + setAnimationLoop = () => {} + domElement = new MockEventListener() + setPixelRatio = () => {} + getSize = () => 0 + getContext = () => {} + getPixelRatio = () => 1 +} + export class MockEngineRenderer extends EngineRenderer { static instance: EngineRenderer constructor() { super() - this.renderer = { - setAnimationLoop: () => {}, - domElement: new MockEventListener() - } as unknown as WebGLRenderer + this.renderer = new MockRenderer() as unknown as WebGLRenderer + this.effectComposer = { + setSize: () => {}, + passes: [{ name: 'RenderPass', overrideMaterial: null }], + setMainScene: () => {}, + setMainCamera: () => {}, + render: () => {} + } as unknown as EffectComposer + this.needsResize = false } }