diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json index 55d241fcc0a..06c84e8032c 100755 --- a/packages/client-core/i18n/en/editor.json +++ b/packages/client-core/i18n/en/editor.json @@ -748,6 +748,21 @@ "lbl-args": "Args", "error-url": "Error Loading Script From URL" }, + "primitiveGeometry":{ + "name": "Primitive Geometry", + "description":"3D models of Basic Geometry shapes", + "lbl-geometryType": "Geometry Type", + "lbl-box": "Box", + "lbl-sphere":"Sphere", + "lbl-cylinder":"Cylinder", + "lbl-capsule":"Capsule", + "lbl-plane":"Plane", + "lbl-circle":"Circle", + "lbl-ring":"Ring", + "lbl-torus":"Torus", + "lbl-polyhedron":"Polyhedron", + "lbl-torusknot":"Torus Knot" + }, "portal": { "lbl-portal": "Linked Portal", "lbl-redirect": "Use Page Redirect", @@ -819,6 +834,7 @@ "EE_model": "Creates objects in the hierarchy. Drag a model from the assets folder into the URL box or drag assets directly from project files into the hierarchy.", "EE_volumetric": "Import volumetric files. Accepts DRCS, UVOL, or Manifest Files. Links to cloud hosting.", "EE_video": "Imports a 2D plane that accepts .mp4, .mkv, .avi", + "EE_variant": "Add model variant for level of detail", "EE_positionalAudio": "Import audio clips, .mp3, .flac, .ogg, .wav, .m4a", "EE_image": "Imports an image into the scene", "GroundPlaneComponent": "Create collision ground plane.", @@ -840,7 +856,9 @@ "SplineTrackComponent": "Creates a spline track.", "SplineComponent": "Create and customize curves.", "EnvmapComponent": "Add environment map to your scene", - "PostProcessingComponent":"Add postprocessing effects to your scene" + "PostProcessingComponent":"Add postprocessing effects to your scene", + "PrimitiveGeometryComponent":"Add basic Geometry to the scene", + "LinkComponent":"Add clickable Link to entity to route to different webpage" } }, "filebrowser": { diff --git a/packages/editor/src/components/element/ElementList.tsx b/packages/editor/src/components/element/ElementList.tsx index dc225c576c7..e14ce4bbaa0 100644 --- a/packages/editor/src/components/element/ElementList.tsx +++ b/packages/editor/src/components/element/ElementList.tsx @@ -67,6 +67,7 @@ import { LinkComponent } from '@etherealengine/engine/src/scene/components/LinkC import { LoadVolumeComponent } from '@etherealengine/engine/src/scene/components/LoadVolumeComponent' import { PostProcessingComponent } from '@etherealengine/engine/src/scene/components/PostProcessingComponent' import { Vector3 } from 'three' +import { PrimitiveGeometryComponent } from '../../../../engine/src/scene/components/PrimitiveGeometryComponent' import { ItemTypes } from '../../constants/AssetTypes' import { EntityNodeEditor } from '../../functions/ComponentEditors' import { EditorControlFunctions } from '../../functions/EditorControlFunctions' @@ -92,7 +93,13 @@ type SceneElementListItemType = { export const ComponentShelfCategories: Record = { Files: [ModelComponent, VolumetricComponent, PositionalAudioComponent, VideoComponent, ImageComponent], - 'Scene Composition': [GroundPlaneComponent, GroupComponent, ColliderComponent, LoadVolumeComponent], + 'Scene Composition': [ + PrimitiveGeometryComponent, + GroundPlaneComponent, + GroupComponent, + ColliderComponent, + LoadVolumeComponent + ], Interaction: [SpawnPointComponent, PortalComponent, LinkComponent], Lighting: [ AmbientLightComponent, @@ -228,7 +235,7 @@ export function ElementList() { { + const { t } = useTranslation() + const entity = props.entity + const hasError = getEntityErrors(entity, PrimitiveGeometryComponent) + const primitiveGeometry = useComponent(entity, PrimitiveGeometryComponent) + const geometry = primitiveGeometry.geometry.get(NO_PROXY) + const renderPrimitiveGeometrySettings = () => ( + (val) => { + const params = primitiveGeometry.geometryParams.get(NO_PROXY) + params[k] = val + commitProperty(PrimitiveGeometryComponent, 'geometryParams')(params) + }} + /> + ) + + return ( + + + { + commitProperty(PrimitiveGeometryComponent, 'geometryType')(value as GeometryTypeEnum) + }} + /> + + {renderPrimitiveGeometrySettings()} + + ) +} + +PrimitiveGeometryNodeEditor.iconComponent = InterestsIcon + +export default PrimitiveGeometryNodeEditor diff --git a/packages/editor/src/functions/ComponentEditors.tsx b/packages/editor/src/functions/ComponentEditors.tsx index 3ede96d22b1..7a60bdbad07 100644 --- a/packages/editor/src/functions/ComponentEditors.tsx +++ b/packages/editor/src/functions/ComponentEditors.tsx @@ -82,6 +82,7 @@ import LoadVolumeNodeEditor from '../components/properties/LoadVolumeNodeEditor' import { LinkComponent } from '@etherealengine/engine/src/scene/components/LinkComponent' import { ShadowComponent } from '@etherealengine/engine/src/scene/components/ShadowComponent' +import { PrimitiveGeometryComponent } from '@etherealengine/engine/src/scene/components/PrimitiveGeometryComponent' import LinkNodeEditor from '../components/properties/LinkNodeEditor' import LoopAnimationNodeEditor from '../components/properties/LoopAnimationNodeEditor' import MediaNodeEditor from '../components/properties/MediaNodeEditor' @@ -94,6 +95,7 @@ import PointLightNodeEditor from '../components/properties/PointLightNodeEditor' import PortalNodeEditor from '../components/properties/PortalNodeEditor' import PositionalAudioNodeEditor from '../components/properties/PositionalAudioNodeEditor' import { PostProcessingSettingsEditor } from '../components/properties/PostProcessingSettingsEditor' +import PrimitiveGeometryNodeEditor from '../components/properties/PrimitiveGeometryNodeEditor' import { RenderSettingsEditor } from '../components/properties/RenderSettingsEditor' import SceneNodeEditor from '../components/properties/SceneNodeEditor' import ScenePreviewCameraNodeEditor from '../components/properties/ScenePreviewCameraNodeEditor' @@ -125,6 +127,8 @@ EntityNodeEditor.set(ModelComponent, ModelNodeEditor) EntityNodeEditor.set(ShadowComponent, ShadowProperties) EntityNodeEditor.set(LoopAnimationComponent, LoopAnimationNodeEditor) EntityNodeEditor.set(ParticleSystemComponent, ParticleSystemNodeEditor) +EntityNodeEditor.set(PrimitiveGeometryComponent, PrimitiveGeometryNodeEditor) + EntityNodeEditor.set(PortalComponent, PortalNodeEditor) EntityNodeEditor.set(MountPointComponent, MountPointNodeEditor) EntityNodeEditor.set(ColliderComponent, ColliderNodeEditor) diff --git a/packages/engine/src/scene/components/PrimitiveGeometryComponent.ts b/packages/engine/src/scene/components/PrimitiveGeometryComponent.ts new file mode 100644 index 00000000000..3f53c7e60c3 --- /dev/null +++ b/packages/engine/src/scene/components/PrimitiveGeometryComponent.ts @@ -0,0 +1,208 @@ +/* +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 { useEffect } from 'react' +import { + BoxGeometry, + CapsuleGeometry, + CircleGeometry, + CylinderGeometry, + DodecahedronGeometry, + Euler, + IcosahedronGeometry, + Mesh, + MeshLambertMaterial, + OctahedronGeometry, + PlaneGeometry, + RingGeometry, + SphereGeometry, + TetrahedronGeometry, + TorusGeometry, + TorusKnotGeometry +} from 'three' + +import { Geometry } from '@etherealengine/engine/src/assets/constants/Geometry' +import { NO_PROXY, useState } from '@etherealengine/hyperflux' +import { defineComponent, useComponent } from '../../ecs/functions/ComponentFunctions' +import { useEntityContext } from '../../ecs/functions/EntityFunctions' +import { TransformComponent } from '../../transform/components/TransformComponent' +import { GeometryTypeEnum } from '../constants/GeometryTypeEnum' +import { ObjectLayers } from '../constants/ObjectLayers' +import { setObjectLayers } from '../functions/setObjectLayers' +import { addObjectToGroup, removeObjectFromGroup } from './GroupComponent' + +export const PrimitiveGeometryComponent = defineComponent({ + name: 'PrimitiveGeometryComponent', + jsonID: 'primitive-geometry', + + onInit: (entity) => { + return { + geometryType: GeometryTypeEnum.BoxGeometry as GeometryTypeEnum, + geometry: null! as Geometry, + geometryParams: null! as Record + } + }, + + toJSON: (entity, component) => { + return { + geometryType: component.geometryType.value, + geometryParams: component.geometryParams.value + } + }, + + onSet: (entity, component, json) => { + if (!json) return + if (typeof json.geometryType === 'number') component.geometryType.set(json.geometryType) + if (typeof json.geometryParams === 'object') component.geometryParams.set(json.geometryParams) + }, + + onRemove: (entity, component) => { + if (component.geometry.value) { + component.geometry.value.dispose() + } + }, + + reactor: GeometryReactor +}) + +function GeometryReactor() { + const entity = useEntityContext() + const geometryComponent = useComponent(entity, PrimitiveGeometryComponent) + const transform = useComponent(entity, TransformComponent) + const material = new MeshLambertMaterial() // set material later + const mesh = useState(new Mesh()) + + function areKeysDifferentTypes(obj1: Record, obj2: Record): boolean { + if (!obj1 || !obj2) { + return true + } + const keys1 = Object.keys(obj1) + const keys2 = Object.keys(obj2) + + if (keys1.length !== keys2.length) { + return true // Objects have different numbers of keys + } + + for (const key of keys1) { + if (!keys2.includes(key)) { + return true // Objects have different keys + } + + if (typeof obj1[key] !== typeof obj2[key]) { + return true // Keys have different types + } + } + + return false // Objects have the same keys with the same types + } + + function createGeometry(geometryType: new (...args: any[]) => Geometry, params: Record): Geometry { + const argList = params ? Object.values(params) : null + let currGeometry = new geometryType() + if (areKeysDifferentTypes(params, (currGeometry as any).parameters)) { + geometryComponent.geometryParams.set((currGeometry as any).parameters) + return currGeometry + } + currGeometry.dispose() + currGeometry = argList ? new geometryType(...argList) : new geometryType() + return currGeometry + } + useEffect(() => { + geometryComponent.geometry.set(new BoxGeometry()) // set default geometry + mesh.set(new Mesh(geometryComponent.geometry.value, material)) + mesh.value.name = `${entity}-primitive-geometry` + mesh.value.visible = true + mesh.value.updateMatrixWorld(true) + setObjectLayers(mesh.value, ObjectLayers.Scene) + addObjectToGroup(entity, mesh.value) + + return () => { + removeObjectFromGroup(entity, mesh.value) + } + }, []) + + useEffect(() => { + if (!mesh) return + + mesh.value.geometry.dispose() + mesh.value.geometry = geometryComponent.geometry.get(NO_PROXY) + mesh.position.value.copy(transform.position.value) + mesh.rotation.value.copy(new Euler().setFromQuaternion(transform.rotation.value)) + mesh.scale.value.copy(transform.scale.value) + }, [geometryComponent.geometry]) + + useEffect(() => { + const params = geometryComponent.geometryParams.get(NO_PROXY) + let currentGeometry: Geometry + // can we get the params for a geometry type before its initialized? that would simplify this + // can we make the GeometryTypeEnum, contain the typeof geometry? that would simplify this switch case into a single line + switch (geometryComponent.geometryType.value) { + case GeometryTypeEnum.BoxGeometry: + currentGeometry = createGeometry(BoxGeometry, params) + break + case GeometryTypeEnum.SphereGeometry: + currentGeometry = createGeometry(SphereGeometry, params) + break + case GeometryTypeEnum.CylinderGeometry: + currentGeometry = createGeometry(CylinderGeometry, params) + break + case GeometryTypeEnum.CapsuleGeometry: + currentGeometry = createGeometry(CapsuleGeometry, params) + break + case GeometryTypeEnum.PlaneGeometry: + currentGeometry = createGeometry(PlaneGeometry, params) + break + case GeometryTypeEnum.CircleGeometry: + currentGeometry = createGeometry(CircleGeometry, params) + break + case GeometryTypeEnum.RingGeometry: + currentGeometry = createGeometry(RingGeometry, params) + break + case GeometryTypeEnum.TorusGeometry: + currentGeometry = createGeometry(TorusGeometry, params) + break + case GeometryTypeEnum.TorusKnotGeometry: + currentGeometry = createGeometry(TorusKnotGeometry, params) + break + case GeometryTypeEnum.DodecahedronGeometry: + currentGeometry = createGeometry(DodecahedronGeometry, params) + break + case GeometryTypeEnum.IcosahedronGeometry: + currentGeometry = createGeometry(IcosahedronGeometry, params) + break + case GeometryTypeEnum.OctahedronGeometry: + currentGeometry = createGeometry(OctahedronGeometry, params) + break + case GeometryTypeEnum.TetrahedronGeometry: + currentGeometry = createGeometry(TetrahedronGeometry, params) + break + default: + return + } + geometryComponent.geometry.set(currentGeometry) + // change the geometry on the model + }, [geometryComponent.geometryType, geometryComponent.geometryParams]) + return null +} diff --git a/packages/engine/src/scene/constants/GeometryTypeEnum.ts b/packages/engine/src/scene/constants/GeometryTypeEnum.ts new file mode 100644 index 00000000000..7c9c9fd3c67 --- /dev/null +++ b/packages/engine/src/scene/constants/GeometryTypeEnum.ts @@ -0,0 +1,40 @@ +/* +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. +*/ + +export enum GeometryTypeEnum { + 'BoxGeometry', + 'SphereGeometry', + 'CylinderGeometry', + 'CapsuleGeometry', + 'PlaneGeometry', + 'CircleGeometry', + 'RingGeometry', + 'TorusGeometry', + 'DodecahedronGeometry', + 'IcosahedronGeometry', + 'OctahedronGeometry', + 'TetrahedronGeometry', + 'TorusKnotGeometry' +}