├── .editorconfig ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── buildspec.yml ├── electron ├── .gitignore └── package.json ├── jest.config.js ├── noop ├── index.js └── package.json ├── package.json ├── packages ├── README.md ├── ammo │ ├── Makefile │ ├── README.md │ └── src │ │ ├── AmmoLoader.interface.ts │ │ ├── AmmoLoader.ts │ │ ├── CollisionFlags.enum.ts │ │ ├── disposableAmmo.ts │ │ └── globals.d.ts ├── bootstrap │ ├── Makefile │ ├── README.md │ └── src │ │ ├── Workers.type.ts │ │ ├── createAtlasService.ts │ │ ├── createI18next.ts │ │ ├── createRenderingService.ts │ │ ├── createScenes.ts │ │ ├── createTexturesService.ts │ │ ├── electron.ts │ │ ├── global.d.ts │ │ ├── index.ts │ │ ├── service_worker.ts │ │ ├── untyped.d.ts │ │ ├── worker_atlas.ts │ │ ├── worker_dynamics.ts │ │ ├── worker_gltf.ts │ │ ├── worker_offscreen.ts │ │ ├── worker_progress.ts │ │ ├── worker_quakemaps.ts │ │ ├── worker_textures.ts │ │ └── workers.ts ├── dom-renderer │ ├── Makefile │ ├── README.md │ └── src │ │ ├── DOMElementProps.type.ts │ │ ├── DOMElementView.interface.ts │ │ ├── DOMElementView.tsx │ │ ├── DOMElementViewBuilder.interface.ts │ │ ├── DOMElementViewContext.type.ts │ │ ├── DOMElementViewHandle.interface.ts │ │ ├── DOMElementViewHandle.ts │ │ ├── DOMElementViewStyler.interface.ts │ │ ├── DOMElementViewStyler.tsx │ │ ├── DOMElementsLookup.type.ts │ │ ├── DOMUIController.interface.ts │ │ ├── DOMUIController.ts │ │ ├── DOMUIControllerState.type.ts │ │ ├── Events.enum.ts │ │ ├── MessageDOMUIDispose.type.ts │ │ ├── MessageDOMUIRender.type.ts │ │ ├── RendererDimensionsManager.interface.ts │ │ ├── RendererDimensionsManager.ts │ │ ├── StatsCollector.interface.ts │ │ ├── StatsCollector.ts │ │ ├── StatsReporterDOMElementView.interface.ts │ │ ├── StatsReporterDOMElementView.tsx │ │ ├── domElementsLookup.ts │ │ ├── isDOMElementView.ts │ │ ├── isDOMElementViewConstructor.ts │ │ └── isHTMLElementConstructor.ts ├── dom │ ├── Makefile │ ├── README.md │ └── src │ │ ├── ConstructableStyleSheetsCache.type.ts │ │ ├── FontPreloadParameters.type.ts │ │ ├── FontPreloadService.interface.ts │ │ ├── FontPreloadService.ts │ │ ├── HTMLElementResizeObserver.interface.ts │ │ ├── HTMLElementResizeObserver.ts │ │ ├── HTMLElementSizeHandle.ts │ │ ├── MessageFontPreload.type.ts │ │ ├── WindowFocusObserver.interface.ts │ │ ├── WindowFocusObserver.ts │ │ ├── WindowFocusObserverState.type.ts │ │ ├── WindowResizeObserver.interface.ts │ │ ├── WindowResizeObserver.ts │ │ ├── clearHTMLElement.ts │ │ ├── createConstructableStyleSheetsCache.ts │ │ ├── createConstructableStylesheet.ts │ │ ├── getHTMLElementById.test.ts │ │ ├── getHTMLElementById.ts │ │ └── preloadImage.ts ├── dynamics │ ├── Makefile │ ├── README.md │ └── src │ │ ├── DynamicsMainLoopTicker.ts │ │ ├── DynamicsWorld.interface.ts │ │ ├── DynamicsWorld.ts │ │ ├── DynamicsWorldInfo.type.ts │ │ ├── DynamicsWorldState.type.ts │ │ ├── DynamicsWorldStatsHook.ts │ │ ├── MessageFeedbackSimulantPreloaded.type.ts │ │ ├── MessageSimulantDispose.type.ts │ │ ├── MessageSimulantRegister.type.ts │ │ ├── MessageSimulantUpdate.type.ts │ │ ├── RigidBodyRemoteHandle.interface.ts │ │ ├── RigidBodyRemoteHandle.ts │ │ ├── Simulant.interface.ts │ │ ├── SimulantFactory.interface.ts │ │ ├── SimulantState.type.ts │ │ ├── SimulantsLookup.type.ts │ │ ├── UserDataHandle.interface.ts │ │ ├── UserDataHandle.ts │ │ ├── UserDataRegistry.interface.ts │ │ ├── UserDataRegistry.test.ts │ │ └── UserDataRegistry.ts ├── expression-language │ ├── Makefile │ ├── README.md │ └── src │ │ ├── Evaluator.interface.ts │ │ ├── Evaluator.test.ts │ │ ├── Evaluator.ts │ │ └── EvaluatorContext.type.ts ├── framework │ ├── Makefile │ ├── README.md │ └── src │ │ ├── ArgumentCallback.type.ts │ │ ├── CameraController.interface.ts │ │ ├── CameraControllerState.type.ts │ │ ├── DefaultMainLoopTicker.ts │ │ ├── DimensionsIndices.enum.ts │ │ ├── DimensionsState.ts │ │ ├── Director.interface.ts │ │ ├── Director.test.ts │ │ ├── Director.ts │ │ ├── DirectorPollablePreloadingObserver.interface.ts │ │ ├── DirectorPollablePreloadingObserver.ts │ │ ├── DirectorState.type.ts │ │ ├── Disposable.interface.ts │ │ ├── DisposableCallback.type.ts │ │ ├── DisposableGeneric.interface.ts │ │ ├── DisposableState.type.ts │ │ ├── EventBus.interface.ts │ │ ├── EventBus.ts │ │ ├── EventBusZoomAmountCallback.type.ts │ │ ├── FallbackScheduler.ts │ │ ├── GenericCallback.type.ts │ │ ├── GeometryAttributes.type.ts │ │ ├── Identifiable.interface.ts │ │ ├── IsUserSettingsValidCallback.type.ts │ │ ├── LocalStorageUserSettingsSync.ts │ │ ├── MainLoop.interface.ts │ │ ├── MainLoop.test.ts │ │ ├── MainLoop.ts │ │ ├── MainLoopStatsHook.interface.ts │ │ ├── MainLoopStatsHook.ts │ │ ├── MainLoopStatsReport.type.ts │ │ ├── MainLoopTicker.interface.ts │ │ ├── MainLoopTickerState.type.ts │ │ ├── MainLoopUpdatable.interface.ts │ │ ├── MainLoopUpdatableState.type.ts │ │ ├── MainLoopUpdateCallback.type.ts │ │ ├── MessageEventData.type.ts │ │ ├── MessageEventHandler.type.ts │ │ ├── MessageEventMultiRouter.type.ts │ │ ├── MessageEventRouter.type.ts │ │ ├── MessageProgress.type.ts │ │ ├── MessageProgressChange.type.ts │ │ ├── MessageProgressDone.type.ts │ │ ├── MessageProgressError.type.ts │ │ ├── MessageProgressExpect.type.ts │ │ ├── MessageProgressStart.type.ts │ │ ├── MessageWorkerReady.type.ts │ │ ├── Mountable.interface.ts │ │ ├── MountableCallback.type.ts │ │ ├── MountableState.type.ts │ │ ├── MultiThreadUserSettingsSync.ts │ │ ├── Nameable.interface.ts │ │ ├── Pauseable.interface.ts │ │ ├── PauseableState.type.ts │ │ ├── PerformanceMemory.type.ts │ │ ├── PerformanceStatsHook.ts │ │ ├── PerformanceStatsReport.type.ts │ │ ├── PollablePreloading.interface.ts │ │ ├── Preloadable.interface.ts │ │ ├── PreloadableCallback.type.ts │ │ ├── PreloadableState.type.ts │ │ ├── Preloader.interface.ts │ │ ├── Preloader.ts │ │ ├── Progress.interface.ts │ │ ├── Progress.ts │ │ ├── ProgressCallback.type.ts │ │ ├── ProgressManager.interface.ts │ │ ├── ProgressManager.ts │ │ ├── ProgressManagerState.type.ts │ │ ├── ProgressState.type.ts │ │ ├── RPCLookupTable.type.ts │ │ ├── RPCMessage.type.ts │ │ ├── RPCResponseHandler.type.ts │ │ ├── RegistersMessagePort.interface.ts │ │ ├── RequestAnimationFrameScheduler.ts │ │ ├── ResizeableRenderer.interface.ts │ │ ├── ReusedResponse.type.ts │ │ ├── ReusedResponsesCache.type.ts │ │ ├── ReusedResponsesUsage.type.ts │ │ ├── Scene.interface.ts │ │ ├── SceneState.type.ts │ │ ├── SceneTransition.interface.ts │ │ ├── SceneTransition.ts │ │ ├── SceneTransitionState.type.ts │ │ ├── Scheduler.interface.ts │ │ ├── SchedulerCallback.type.ts │ │ ├── Service.interface.ts │ │ ├── ServiceBuilder.interface.ts │ │ ├── ServiceBuilder.test.ts │ │ ├── ServiceBuilder.ts │ │ ├── ServiceManager.interface.ts │ │ ├── ServiceManager.ts │ │ ├── SetTimeoutScheduler.ts │ │ ├── SingleThreadMessageChannel.test.ts │ │ ├── SingleThreadMessageChannel.ts │ │ ├── StatsHook.interface.ts │ │ ├── StatsReport.type.ts │ │ ├── StatsReporter.interface.ts │ │ ├── StatsReporter.ts │ │ ├── SupportChecker.type.ts │ │ ├── TickTimerState.type.ts │ │ ├── UnmountableCallback.type.ts │ │ ├── UserSettings.ts │ │ ├── UserSettings.type.ts │ │ ├── UserSettingsManager.interface.ts │ │ ├── UserSettingsManagerState.type.ts │ │ ├── UserSettingsSync.interface.ts │ │ ├── WebGLRendererStatsHook.ts │ │ ├── WebGLRendererStatsReport.type.ts │ │ ├── WorkerServiceClient.interface.ts │ │ ├── WorkerServiceClient.ts │ │ ├── attachMultiRouter.test.ts │ │ ├── attachMultiRouter.ts │ │ ├── createEmptyMesh.ts │ │ ├── createMultiThreadMessageChannel.ts │ │ ├── createRPCLookupTable.ts │ │ ├── createReusedResponsesCache.ts │ │ ├── createReusedResponsesUsage.ts │ │ ├── createRouter.test.ts │ │ ├── createRouter.ts │ │ ├── createSettingsHandle.test.ts │ │ ├── createSettingsHandle.ts │ │ ├── createSingleThreadMessageChannel.ts │ │ ├── disposableGeneric.ts │ │ ├── disposableMaterial.ts │ │ ├── disposableMesh.ts │ │ ├── dispose.ts │ │ ├── disposeAll.ts │ │ ├── disposeWebGLRenderTarget.ts │ │ ├── extractGeometryAttributes.ts │ │ ├── findAllMeshes.ts │ │ ├── findMesh.ts │ │ ├── first.test.ts │ │ ├── first.ts │ │ ├── getRouterCallback.test.ts │ │ ├── getRouterCallback.ts │ │ ├── handleRPCResponse.test.ts │ │ ├── handleRPCResponse.ts │ │ ├── invoke.test.ts │ │ ├── invoke.ts │ │ ├── isCanvasTransferControlToOffscreenSupported.ts │ │ ├── isConstructableCSSStyleSheetSupported.ts │ │ ├── isCreateImageBitmapSupported.ts │ │ ├── isCustomEvent.ts │ │ ├── isInDimensionsBounds.ts │ │ ├── isMesh.ts │ │ ├── isMountable.ts │ │ ├── isNameable.ts │ │ ├── isPollablePreloading.ts │ │ ├── isPromise.ts │ │ ├── isRequestAnimationFrameSupported.ts │ │ ├── isSharedArrayBuffer.ts │ │ ├── isSharedArrayBufferSupported.ts │ │ ├── isStatsReport.ts │ │ ├── longestVector3.test.ts │ │ ├── longestVector3.ts │ │ ├── monitorResponseProgress.ts │ │ ├── mount.ts │ │ ├── mountAll.ts │ │ ├── must.test.ts │ │ ├── must.ts │ │ ├── name.ts │ │ ├── noop.ts │ │ ├── onlyOne.ts │ │ ├── passiveEventListener.ts │ │ ├── pause.ts │ │ ├── prefetch.ts │ │ ├── preload.ts │ │ ├── reuseResponse.ts │ │ ├── routeMessages.test.ts │ │ ├── routeMessages.ts │ │ ├── routeMultiMessage.ts │ │ ├── sendRPCMessage.ts │ │ ├── unary.test.ts │ │ ├── unary.ts │ │ ├── unmount.ts │ │ ├── unmountAll.ts │ │ ├── unpause.ts │ │ ├── updateOrthographicCameraAspect.ts │ │ └── updatePerspectiveCameraAspect.ts ├── i18n │ ├── Makefile │ ├── README.md │ └── src │ │ ├── InternationalizationService.interface.ts │ │ ├── InternationalizationService.ts │ │ ├── LoglevelPlugin.ts │ │ ├── getI18NextKeyNamespace.test.ts │ │ └── getI18NextKeyNamespace.ts ├── input │ ├── Makefile │ ├── README.md │ └── src │ │ ├── KeyboardIndices.enum.ts │ │ ├── KeyboardKeyName.type.ts │ │ ├── KeyboardObserver.interface.ts │ │ ├── KeyboardObserver.ts │ │ ├── KeyboardObserverState.type.ts │ │ ├── KeyboardState.ts │ │ ├── MouseButtons.enum.ts │ │ ├── MouseIndices.enum.ts │ │ ├── MouseObserver.interface.ts │ │ ├── MouseObserver.ts │ │ ├── MouseObserverState.type.ts │ │ ├── MouseState.ts │ │ ├── MouseWheelObserver.interface.ts │ │ ├── MouseWheelObserver.ts │ │ ├── PointerTouch.type.ts │ │ ├── Raycastable.interface.ts │ │ ├── RaycastableState.type.ts │ │ ├── Raycaster.interface.ts │ │ ├── Raycaster.ts │ │ ├── RaycasterState.type.ts │ │ ├── TouchIndices.enum.ts │ │ ├── TouchObserver.interface.ts │ │ ├── TouchObserver.ts │ │ ├── TouchObserverState.type.ts │ │ ├── TouchState.ts │ │ ├── UserInputController.interface.ts │ │ ├── UserInputControllerState.type.ts │ │ ├── UserInputMouseController.interface.ts │ │ ├── UserInputMouseControllerState.type.ts │ │ ├── computePointerStretchVector.test.ts │ │ ├── computePointerStretchVector.ts │ │ ├── computePrimaryTouchStretchVector.ts │ │ ├── getMousePointerVectorX.ts │ │ ├── getMousePointerVectorY.ts │ │ ├── getPointerVectorX.ts │ │ ├── getPointerVectorY.ts │ │ ├── getPrimaryPointerVectorX.ts │ │ ├── getPrimaryPointerVectorY.ts │ │ ├── getPrimaryTouchInitialClientX.ts │ │ ├── getPrimaryTouchInitialClientY.ts │ │ ├── getPrimaryTouchVectorX.ts │ │ ├── getPrimaryTouchVectorY.ts │ │ ├── isKeyboardKeyName.ts │ │ ├── isMousePointerInDimensionsBounds.ts │ │ ├── isPotentiallyMouseClick.ts │ │ ├── isPrimaryMouseButtonPressInitiatedByRootElement.ts │ │ ├── isPrimaryMouseButtonPressed.ts │ │ ├── isPrimaryPointerPressed.ts │ │ ├── isPrimaryTouchInDimensionsBounds.ts │ │ ├── isPrimaryTouchInitiatedByRootElement.ts │ │ └── isPrimaryTouchPressed.ts ├── math │ ├── Makefile │ ├── README.md │ └── src │ │ ├── clamp.ts │ │ ├── damp.ts │ │ ├── generateUUID.ts │ │ ├── isPowerOfTwo.ts │ │ ├── lerp.ts │ │ ├── radToDeg.ts │ │ ├── randFloat.ts │ │ ├── randInt.ts │ │ ├── roundToNearestMultiple.test.ts │ │ └── roundToNearestMultiple.ts ├── personalidol │ ├── Makefile │ ├── README.md │ └── src │ │ ├── AmbientLightView.ts │ │ ├── AnyEntity.type.ts │ │ ├── BackgroundLightUserSettingsManager.ts │ │ ├── BuildGeometryAttributesCallback.type.ts │ │ ├── ButtonDOMElementView.tsx │ │ ├── CameraController.ts │ │ ├── CameraParameters.enum.ts │ │ ├── CharacterView.interface.ts │ │ ├── CharacterViewState.type.ts │ │ ├── DOMBreakpoints.enum.ts │ │ ├── DOMElementViewBuilder.ts │ │ ├── DOMElementViewContext.type.ts │ │ ├── DOMElementsLookup.type.ts │ │ ├── DOMZIndex.enum.ts │ │ ├── Entity.type.ts │ │ ├── EntityController.interface.ts │ │ ├── EntityControllerBag.interface.ts │ │ ├── EntityControllerBag.ts │ │ ├── EntityControllerBagState.type.ts │ │ ├── EntityControllerFactory.interface.ts │ │ ├── EntityControllerFactory.ts │ │ ├── EntityControllerState.type.ts │ │ ├── EntityFuncGroup.type.ts │ │ ├── EntityGLTFModel.type.ts │ │ ├── EntityLight.type.ts │ │ ├── EntityLightAmbient.type.ts │ │ ├── EntityLightHemisphere.type.ts │ │ ├── EntityLightPoint.type.ts │ │ ├── EntityLightSpotlight.type.ts │ │ ├── EntityLookup.type.ts │ │ ├── EntityLookupCallback.type.ts │ │ ├── EntityLookupTable.type.ts │ │ ├── EntityPlayer.type.ts │ │ ├── EntityScriptedBrush.type.ts │ │ ├── EntityScriptedZone.type.ts │ │ ├── EntitySounds.type.ts │ │ ├── EntitySparkParticles.type.ts │ │ ├── EntityTarget.type.ts │ │ ├── EntityView.interface.ts │ │ ├── EntityViewFactory.interface.ts │ │ ├── EntityViewFactory.ts │ │ ├── EntityViewState.type.ts │ │ ├── EntityWithController.type.ts │ │ ├── EntityWithObjectLabel.type.ts │ │ ├── EntityWorldspawn.type.ts │ │ ├── FatalErrorDOMElementView.tsx │ │ ├── FormRadioButtonsDOMElementView.tsx │ │ ├── FormRangeSliderDOMElementView.tsx │ │ ├── GameState.ts │ │ ├── GameState.type.ts │ │ ├── GameStateController.interface.ts │ │ ├── GameStateController.ts │ │ ├── GeometryWithBrushesEntity.type.ts │ │ ├── GhostZoneSimulant.ts │ │ ├── HemisphereLightView.ts │ │ ├── InGameMenuDOMElementView.tsx │ │ ├── InGameMenuTriggerDOMElementView.tsx │ │ ├── InstancedGLTFModelView.ts │ │ ├── InstancedGLTFModelViewManager.interface.ts │ │ ├── InstancedGLTFModelViewManager.ts │ │ ├── InstancedMeshHandle.interface.ts │ │ ├── InstancedMeshHandle.ts │ │ ├── InstancedMeshHandleState.type.ts │ │ ├── InteractableEntityController.ts │ │ ├── InteractorEntityController.ts │ │ ├── LanguageSettingsDOMElementView.tsx │ │ ├── LanguageUserSettingsManager.ts │ │ ├── LoadingScreenScene.ts │ │ ├── LocationMapEntityFilter.ts │ │ ├── LocationMapScene.interface.ts │ │ ├── LocationMapScene.ts │ │ ├── MainMenuButtonDOMElementView.tsx │ │ ├── MainMenuDOMElementView.tsx │ │ ├── MainMenuLanguageButtonDOMElementView.tsx │ │ ├── MainMenuLayoutDOMElementView.tsx │ │ ├── MainMenuScene.interface.ts │ │ ├── MainMenuScene.ts │ │ ├── MainMenuUserSettingsButtonDOMElementView.tsx │ │ ├── MapTransitionEntityController.ts │ │ ├── MeshUserSettingsManager.ts │ │ ├── MessageGameStateChange.type.ts │ │ ├── MessageUIStateChange.type.ts │ │ ├── MousePointerLayerDOMElementView.tsx │ │ ├── NPCEntity.type.ts │ │ ├── NPCEntityController.interface.ts │ │ ├── NPCEntityController.ts │ │ ├── NPCSimulant.ts │ │ ├── ObjectInteractionsDOMElementView.tsx │ │ ├── ObjectLabelDOMElementView.tsx │ │ ├── PlayerEntityController.interface.ts │ │ ├── PlayerEntityController.ts │ │ ├── PlayerView.ts │ │ ├── PointLightView.ts │ │ ├── ProgressManagerStateDOMElementView.tsx │ │ ├── ReloadButtonDOMElementView.tsx │ │ ├── ScriptedZoneView.ts │ │ ├── SettingsBackdropDOMElementView.tsx │ │ ├── ShadowLightUserSettingsManager.ts │ │ ├── SimulantFactory.ts │ │ ├── SimulantsLookup.type.ts │ │ ├── SpotlightLightView.ts │ │ ├── StructureCeilingEntityController.ts │ │ ├── TargetOrTargetedEntity.type.ts │ │ ├── TargetView.ts │ │ ├── TargetedEntity.type.ts │ │ ├── TargetingEntity.type.ts │ │ ├── UIState.ts │ │ ├── UIState.type.ts │ │ ├── UIStateController.interface.ts │ │ ├── UIStateController.ts │ │ ├── UIStateControllerInfo.type.ts │ │ ├── UIStateControllerStatsHook.ts │ │ ├── UIStateControllerStatsReport.type.ts │ │ ├── UserInputEventBusController.ts │ │ ├── UserInputKeyboardController.ts │ │ ├── UserInputMouseController.ts │ │ ├── UserInputTouchController.ts │ │ ├── UserSettings.ts │ │ ├── UserSettings.type.ts │ │ ├── UserSettingsDOMElementView.tsx │ │ ├── UserSettingsDynamicLightQualityMap.enum.ts │ │ ├── ViewBuildingStep.type.ts │ │ ├── VirtualJoystickLayerDOMElementView.tsx │ │ ├── WebGLRendererUserSettingsManager.ts │ │ ├── WorldMapScene.interface.ts │ │ ├── WorldMapScene.ts │ │ ├── WorldspawnGeometryEntityController.ts │ │ ├── WorldspawnGeometrySimulant.ts │ │ ├── WorldspawnGeometryView.ts │ │ ├── buildEntities.ts │ │ ├── buildViews.ts │ │ ├── createEntityControllerState.ts │ │ ├── createEntityViewState.ts │ │ ├── createViewBuildingPlan.test.ts │ │ ├── createViewBuildingPlan.ts │ │ ├── domElementsLookup.ts │ │ ├── global.d.ts │ │ ├── globals.d.ts │ │ ├── isCharacterView.ts │ │ ├── isEntityOfClass.ts │ │ ├── isEntityView.ts │ │ ├── isEntityViewOfClass.ts │ │ ├── isEntityWithController.ts │ │ ├── isEntityWithObjectLabel.ts │ │ ├── isMainMenuScene.ts │ │ ├── isNPCEntityView.ts │ │ ├── isTarget.ts │ │ ├── isTargetedBy.ts │ │ ├── isTargeting.ts │ │ ├── isUserSettingsValid.ts │ │ └── useObjectLabel.ts ├── quakemaps │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── fixtures │ │ ├── map-mountain-caravan.map │ │ ├── map-test-textured.map │ │ └── map-test.map │ └── src │ │ ├── Brush.type.ts │ │ ├── BrushHalfSpaceTriangle.type.ts │ │ ├── EntityProperties.type.ts │ │ ├── EntitySketch.type.ts │ │ ├── EntitySketchProperty.type.ts │ │ ├── EntityType.type.ts │ │ ├── Exception.ts │ │ ├── Geometry.type.ts │ │ ├── HalfSpace.type.ts │ │ ├── IntersectingPointsCache.type.ts │ │ ├── ParserException.ts │ │ ├── TextureDimensionsResolver.type.ts │ │ ├── TextureUrlResolver.type.ts │ │ ├── TriangleSimple.type.ts │ │ ├── UnmarshalException.ts │ │ ├── Vector3Simple.type.ts │ │ ├── buildGeometryAttributes.test.ts │ │ ├── buildGeometryAttributes.ts │ │ ├── buildGeometryPoints.ts │ │ ├── buildGeometryTriangles.ts │ │ ├── fixBrushAfterDeserialization.ts │ │ ├── getCenterOfMass.test.ts │ │ ├── getCenterOfMass.ts │ │ ├── getIntersectingPoint.test.ts │ │ ├── getIntersectingPoint.ts │ │ ├── isAlmostEqual.ts │ │ ├── isEmptyString.ts │ │ ├── marshalCoords.ts │ │ ├── marshalVector3.test.ts │ │ ├── marshalVector3.ts │ │ ├── sanitizeVector3.ts │ │ ├── sortPointsCounterClockwise.test.ts │ │ ├── sortPointsCounterClockwise.ts │ │ ├── triangulateFacePoints.test.ts │ │ ├── triangulateFacePoints.ts │ │ ├── unmarshalEntityProperty.ts │ │ ├── unmarshalHalfSpace.test.ts │ │ ├── unmarshalHalfSpace.ts │ │ ├── unmarshalMap.ts │ │ ├── unmarshalVector3.test.ts │ │ └── unmarshalVector3.ts ├── service-worker │ ├── Makefile │ ├── README.md │ └── src │ │ ├── ServiceWorkerManager.interface.ts │ │ └── ServiceWorkerManager.ts ├── texture-loader │ ├── Makefile │ ├── README.md │ └── src │ │ ├── Atlas.type.ts │ │ ├── AtlasResponse.type.ts │ │ ├── AtlasService.interface.ts │ │ ├── AtlasService.ts │ │ ├── AtlasTextureDimension.type.ts │ │ ├── AtlasTextureDimensions.type.ts │ │ ├── DOMTextureService.interface.ts │ │ ├── DOMTextureService.ts │ │ ├── ImageBitmapResponse.type.ts │ │ ├── ImageDataBufferResponse.type.ts │ │ ├── THREETextureLoader.ts │ │ ├── TextureRequest.type.ts │ │ ├── attachAtlasSamplerToStandardShader.ts │ │ ├── canvas2DDrawImage.ts │ │ ├── createTextureReceiverMessagesRouter.ts │ │ ├── imageDataBufferResponseToImageData.ts │ │ ├── imageDataBufferResponseToTexture.ts │ │ ├── imageToTexture.ts │ │ ├── isImageBitmap.ts │ │ ├── isImageData.ts │ │ ├── keyFromTextureRequest.ts │ │ └── requestTexture.ts ├── three-css2d-renderer │ ├── Makefile │ ├── README.md │ └── src │ │ ├── CSS2DObject.interface.ts │ │ ├── CSS2DObject.ts │ │ ├── CSS2DObjectState.ts │ │ ├── CSS2DObjectStateIndices.enum.ts │ │ ├── CSS2DRenderer.interface.ts │ │ ├── CSS2DRenderer.ts │ │ ├── CSS2DRendererInfo.type.ts │ │ ├── CSS2DRendererStatsHook.ts │ │ ├── CSS2DRendererStatsReport.type.ts │ │ └── isCSS2DObject.ts ├── three-modules │ ├── Makefile │ ├── README.md │ └── src │ │ ├── loaders │ │ ├── GeometryBoundingBox.type.ts │ │ ├── MD2GeometryParts.type.ts │ │ ├── MD2Loader.ts │ │ ├── MD2LoaderMorphNormal.type.ts │ │ ├── MD2LoaderMorphPosition.type.ts │ │ ├── MD2LoaderParsedGeometry.type.ts │ │ ├── MD2LoaderParsedGeometryFrame.type.ts │ │ └── MD2LoaderParsedGeometryWithParts.type.ts │ │ ├── misc │ │ ├── MorphBlendMesh.interface.ts │ │ ├── MorphBlendMesh.ts │ │ └── MorphBlendMeshAnimation.type.ts │ │ ├── postprocessing │ │ ├── ClearMaskPass.ts │ │ ├── EffectComposer.interface.ts │ │ ├── EffectComposer.ts │ │ ├── FullScreenQuad.ts │ │ ├── GlitchPass.ts │ │ ├── MaskPass.ts │ │ ├── Pass.interface.ts │ │ ├── Pass.ts │ │ ├── RenderPass.ts │ │ └── ShaderPass.ts │ │ └── unmountPass.ts ├── three-morph-blend-mesh-mixer │ ├── Makefile │ ├── README.md │ └── src │ │ ├── MorphBlendMeshMixer.interface.ts │ │ ├── MorphBlendMeshMixer.ts │ │ ├── MorphBlendMeshMixerState.type.ts │ │ ├── MorphBlendMeshTransition.interface.ts │ │ ├── MorphBlendMeshTransition.ts │ │ └── MorphBlendMeshTransitionState.type.ts └── views │ ├── Makefile │ ├── README.md │ └── src │ ├── Interactable.interface.ts │ ├── InteractableBag.interface.ts │ ├── InteractableBag.ts │ ├── InteractableState.type.ts │ ├── View.interface.ts │ ├── ViewBag.interface.ts │ ├── ViewBag.ts │ ├── ViewBagState.type.ts │ ├── ViewState.type.ts │ ├── createViewState.ts │ └── isInteractable.ts ├── postcss.config.js ├── public ├── .gitignore ├── index.mustache ├── locales │ ├── en │ │ ├── characters.json │ │ ├── interactions.json │ │ ├── objects.json │ │ └── ui.json │ ├── pl │ │ ├── characters.json │ │ ├── interactions.json │ │ ├── objects.json │ │ └── ui.json │ └── ru │ │ ├── characters.json │ │ ├── interactions.json │ │ ├── objects.json │ │ └── ui.json ├── manifest.json ├── preload.js └── style.css ├── scripts ├── _notify.sh ├── build_mustache.js ├── helper_build_id.sh └── watch_trigger.sh ├── tiled ├── Makefile ├── README.md └── fixtures │ ├── map-northern-creek.tmx │ ├── tileset-icons-crafts-tools-foraging.tsx │ ├── tileset-icons-food-spices-drinks.tsx │ ├── tileset-icons-gems-stone-minerals.tsx │ ├── tileset-icons-old-west-cowboys-mines.tsx │ ├── tileset-icons-strategic-resources-goods.tsx │ ├── tileset-icons-wood-timber-lumber.tsx │ ├── tileset-locations-medieval-fantasy-decor.tsx │ ├── tileset-locations-medieval-fantasy-hexes.tsx │ ├── tileset-locations-medieval-fantasy-roads.tsx │ ├── tileset-meta-passability.tsx │ ├── tileset-terrain-cold-hexes.tsx │ ├── tileset-terrain-desert-decor.tsx │ ├── tileset-terrain-desert-hexes.tsx │ ├── tileset-terrain-plains-decor.tsx │ ├── tileset-terrain-plains-hexes.tsx │ ├── tileset-terrain-tropics-hexes.tsx │ ├── tileset-terrain-volcanic-wastes-decor.tsx │ └── tileset-terrain-volcanic-wastes-hexes.tsx ├── trenchbroom ├── GameConfig.cfg ├── Makefile └── PersonalIdol.fgd ├── tsconfig.json └── yarn.lock /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ master ] 9 | schedule: 10 | - cron: '28 13 * * 0' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'javascript' ] 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v2 25 | 26 | - name: Initialize CodeQL 27 | uses: github/codeql-action/init@v1 28 | with: 29 | languages: ${{ matrix.language }} 30 | 31 | - name: Perform CodeQL Analysis 32 | uses: github/codeql-action/analyze@v1 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # misc 5 | .DS_Store 6 | *~ 7 | 8 | # package managers 9 | /.yarn 10 | /.pnp.js 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Personal Idol (work in progress) 2 | 3 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/distantmagic/personalidol.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/distantmagic/personalidol/alerts/) 4 | [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/distantmagic/personalidol.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/distantmagic/personalidol/context:javascript) 5 | 6 | ## Docs 7 | 8 | Documentation is also a work in progress: 9 | 10 | https://docs.distantmagic.com 11 | 12 | ## Support 13 | 14 | If you need support, feel free to open issue on GitHub. 15 | 16 | If you need 1:1 support via call, you can find me on Codementor. 17 | 18 | [![Contact me on Codementor](https://www.codementor.io/m-badges/matcha/im-a-cm-b.svg)](https://www.codementor.io/@matcha?refer=badge) 19 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | build: 5 | commands: 6 | - make release 7 | 8 | artifacts: 9 | name: server 10 | files: 11 | - "**/*" 12 | base-directory: packages/website/public 13 | -------------------------------------------------------------------------------- /electron/.gitignore: -------------------------------------------------------------------------------- 1 | /main.js 2 | /public 3 | -------------------------------------------------------------------------------- /electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "personalidol", 3 | "version": "1.0.0", 4 | "main": "main.js", 5 | "author": "distantmagic.com", 6 | "license": "GPL-3.0" 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | testPathIgnorePatterns: [ 5 | "/lib/", 6 | "/node_modules/", 7 | ], 8 | transform: { 9 | "^.+\\.(js|ts|tsx)$": "ts-jest" 10 | }, 11 | transformIgnorePatterns: [ 12 | "/node_modules/(?!three)", 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /noop/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | throw new Error("Something tried to use the noop package"); 3 | }; 4 | -------------------------------------------------------------------------------- /noop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noop", 3 | "private": true, 4 | "version": "0.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 | # Packages 2 | 3 | Frontend part is ultimately combined and managed in the `website` package. 4 | -------------------------------------------------------------------------------- /packages/ammo/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/ammo/README.md: -------------------------------------------------------------------------------- 1 | # `ammo` 2 | 3 | This package contains facades around `ammo.js` package to make it compatible 4 | with the `framework` and other packages. 5 | -------------------------------------------------------------------------------- /packages/ammo/src/AmmoLoader.interface.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export interface AmmoLoader { 4 | loadWASM(): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /packages/ammo/src/AmmoLoader.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { must } from "../../framework/src/must"; 4 | 5 | import type { AmmoLoader as IAmmoLoader } from "./AmmoLoader.interface"; 6 | 7 | /** 8 | * Warning: this expects Ammo loader object in the global scope. 9 | */ 10 | export function AmmoLoader(wasmFilename: string): IAmmoLoader { 11 | function _locateFile(): string { 12 | return wasmFilename; 13 | } 14 | 15 | async function loadWASM(): Promise { 16 | const Loaded = await (globalThis.Ammo as unknown as any)({ 17 | locateFile: _locateFile, 18 | }); 19 | 20 | return must(Loaded, "Unable to create AMMO.js instance."); 21 | } 22 | 23 | return Object.freeze({ 24 | loadWASM: loadWASM, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /packages/ammo/src/CollisionFlags.enum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Since collision flags are not translated from the original bullet source 3 | * files, this enum is here. 4 | */ 5 | export enum CollisionFlags { 6 | CF_STATIC_OBJECT = 1, 7 | CF_KINEMATIC_OBJECT = 2, 8 | CF_NO_CONTACT_RESPONSE = 4, 9 | CF_CUSTOM_MATERIAL_CALLBACK = 8, 10 | CF_CHARACTER_OBJECT = 16, 11 | CF_DISABLE_VISUALIZE_OBJECT = 32, 12 | CF_DISABLE_SPU_COLLISION_PROCESSING = 64, 13 | CF_HAS_CONTACT_STIFFNESS_DAMPING = 128, 14 | CF_HAS_CUSTOM_DEBUG_RENDERING_COLOR = 256, 15 | CF_HAS_FRICTION_ANCHOR = 512, 16 | CF_HAS_COLLISION_SOUND_TRIGGER = 1024, 17 | } 18 | -------------------------------------------------------------------------------- /packages/ammo/src/disposableAmmo.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import type { DisposableCallback } from "../../framework/src/DisposableCallback.type"; 4 | 5 | export function disposableAmmo(ammo: typeof Ammo, ammoObject: Ammo.Type): DisposableCallback { 6 | return function () { 7 | ammo.destroy(ammoObject); 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /packages/ammo/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare const __CACHE_BUST: string; 2 | declare const __STATIC_BASE_PATH: string; 3 | -------------------------------------------------------------------------------- /packages/bootstrap/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | rm -rf ./public/index.html 4 | rm -rf ./public/lib 5 | rm -rf ./public/service_worker.js 6 | rm -rf ./public/service_worker_*.js.map 7 | -------------------------------------------------------------------------------- /packages/bootstrap/README.md: -------------------------------------------------------------------------------- 1 | # `website` 2 | 3 | The package to combine them all (at least frontend ones) and start everyting in 4 | the browser. 5 | 6 | Everything is combined in the `src/index.ts` file. It decides which workers to 7 | start, what browser features are supported to provide the best user experience 8 | possible. 9 | -------------------------------------------------------------------------------- /packages/bootstrap/src/Workers.type.ts: -------------------------------------------------------------------------------- 1 | export type Workers = { 2 | [key: string]: { 3 | name: string; 4 | url: string; 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/bootstrap/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare var __ASSETS_BASE_PATH: string; 2 | declare var __BUILD_ID: string; 3 | declare var __CACHE_BUST: string; 4 | declare var __LOCALES_LOAD_PATH: string; 5 | declare var __LOG_LEVEL: "debug" | "info" | "trace"; 6 | declare var __SERVICE_WORKER_BASE_PATH: string; 7 | declare var __STATIC_BASE_PATH: string; 8 | -------------------------------------------------------------------------------- /packages/bootstrap/src/untyped.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@personalidol/three-modules/src/loaders/GLTFLoader" { 2 | export var GLTFLoader: any; 3 | } 4 | -------------------------------------------------------------------------------- /packages/dom-renderer/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/dom-renderer/README.md: -------------------------------------------------------------------------------- 1 | # `dom-renderer` 2 | 3 | This package contains utilities that handle DOM rendering. By default it's the 4 | user interface (menus, buttons, etc), as it is often impractical to create 5 | those in the 3D context. 6 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/DOMElementProps.type.ts: -------------------------------------------------------------------------------- 1 | export type DOMElementProps = { 2 | version: number; 3 | } & { 4 | [key: string]: any; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/DOMElementViewBuilder.interface.ts: -------------------------------------------------------------------------------- 1 | import type { i18n } from "i18next"; 2 | import type { Logger } from "loglevel"; 3 | 4 | import type { DOMElementView } from "./DOMElementView.interface"; 5 | import type { DOMElementViewContext } from "./DOMElementViewContext.type"; 6 | 7 | export interface DOMElementViewBuilder { 8 | initialize(domElementView: DOMElementView, domMessagePort: MessagePort, i18next: i18n, logger: Logger): void; 9 | } 10 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/DOMElementViewContext.type.ts: -------------------------------------------------------------------------------- 1 | export type DOMElementViewContext = { 2 | readonly isDOMElementViewContext: true; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/DOMElementViewHandle.interface.ts: -------------------------------------------------------------------------------- 1 | export interface DOMElementViewHandle { 2 | enable(isEnabled: boolean): void; 3 | } 4 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/DOMElementViewStyler.interface.ts: -------------------------------------------------------------------------------- 1 | import type { VNode } from "preact"; 2 | 3 | export interface DOMElementViewStyler { 4 | readonly isDOMElementViewStyler: true; 5 | 6 | connectedCallback(shadow: ShadowRoot): void; 7 | 8 | render(): null | VNode; 9 | } 10 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/DOMElementsLookup.type.ts: -------------------------------------------------------------------------------- 1 | import type { StatsReporterDOMElementView } from "./StatsReporterDOMElementView"; 2 | 3 | export type DOMElementsLookup = { 4 | "pi-stats-reporter": typeof StatsReporterDOMElementView; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/DOMUIControllerState.type.ts: -------------------------------------------------------------------------------- 1 | import type { PreloadableState } from "../../framework/src/PreloadableState.type"; 2 | 3 | export type DOMUIControllerState = PreloadableState; 4 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/Events.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Events { 2 | elementConnected = "pi-element-connected", 3 | elementDisconnected = "pi-element-disconnected", 4 | } 5 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/MessageDOMUIDispose.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageDOMUIDispose = Array; 2 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/MessageDOMUIRender.type.ts: -------------------------------------------------------------------------------- 1 | import type { DOMElementProps } from "./DOMElementProps.type"; 2 | import type { DOMElementsLookup } from "./DOMElementsLookup.type"; 3 | 4 | export type MessageDOMUIRender = { 5 | element: string & keyof L; 6 | id: string; 7 | props: DOMElementProps; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/RendererDimensionsManager.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | 3 | export interface RendererDimensionsManager extends MainLoopUpdatable { 4 | readonly isRendererDimensionsManager: true; 5 | } 6 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/StatsCollector.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { RegistersMessagePort } from "../../framework/src/RegistersMessagePort.interface"; 3 | import type { Service } from "../../framework/src/Service.interface"; 4 | 5 | export interface StatsCollector extends MainLoopUpdatable, RegistersMessagePort, Service {} 6 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/StatsReporterDOMElementView.interface.ts: -------------------------------------------------------------------------------- 1 | import type { DOMElementView } from "./DOMElementView.interface"; 2 | import type { DOMElementViewContext } from "./DOMElementViewContext.type"; 3 | 4 | export interface StatsReporterDOMElementView extends DOMElementView {} 5 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/domElementsLookup.ts: -------------------------------------------------------------------------------- 1 | import { StatsReporterDOMElementView } from "./StatsReporterDOMElementView"; 2 | 3 | import type { DOMElementsLookup } from "./DOMElementsLookup.type"; 4 | 5 | export const domElementsLookup: DOMElementsLookup = Object.freeze({ 6 | "pi-stats-reporter": StatsReporterDOMElementView, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/isDOMElementView.ts: -------------------------------------------------------------------------------- 1 | import { DOMElementView } from "./DOMElementView"; 2 | 3 | import type { DOMElementView as IDOMElementView } from "./DOMElementView.interface"; 4 | import type { DOMElementViewContext } from "./DOMElementViewContext.type"; 5 | 6 | export function isDOMElementView(item: HTMLElement): item is IDOMElementView { 7 | return item instanceof DOMElementView; 8 | } 9 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/isDOMElementViewConstructor.ts: -------------------------------------------------------------------------------- 1 | import { DOMElementView } from "./DOMElementView"; 2 | import { isHTMLElementConstructor } from "./isHTMLElementConstructor"; 3 | 4 | export function isDOMElementViewConstructor(item: any): item is typeof DOMElementView { 5 | if ("function" !== typeof item) { 6 | return false; 7 | } 8 | 9 | if (!isHTMLElementConstructor(item)) { 10 | return false; 11 | } 12 | 13 | return item.prototype instanceof DOMElementView; 14 | } 15 | -------------------------------------------------------------------------------- /packages/dom-renderer/src/isHTMLElementConstructor.ts: -------------------------------------------------------------------------------- 1 | export function isHTMLElementConstructor(item: any): item is typeof HTMLElement { 2 | if ("function" !== typeof item) { 3 | return false; 4 | } 5 | 6 | return item.prototype instanceof HTMLElement; 7 | } 8 | -------------------------------------------------------------------------------- /packages/dom/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/dom/README.md: -------------------------------------------------------------------------------- 1 | # `dom` 2 | 3 | This package contains utilities and facades around DOM elements that make them 4 | compatible with `framework` interfaces. 5 | -------------------------------------------------------------------------------- /packages/dom/src/ConstructableStyleSheetsCache.type.ts: -------------------------------------------------------------------------------- 1 | export type ConstructableStyleSheetsCache = { 2 | [key: string]: CSSStyleSheet; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/dom/src/FontPreloadParameters.type.ts: -------------------------------------------------------------------------------- 1 | export type FontPreloadParameters = { 2 | family: string; 3 | source: string; 4 | descriptors: { 5 | [key: string]: string; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/dom/src/FontPreloadService.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from "../../framework/src/Service.interface"; 2 | 3 | export interface FontPreloadService extends Service {} 4 | -------------------------------------------------------------------------------- /packages/dom/src/HTMLElementResizeObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | export interface HTMLElementResizeObserver extends MainLoopUpdatable, Service { 5 | isHTMLElementResizeObserver: true; 6 | } 7 | -------------------------------------------------------------------------------- /packages/dom/src/HTMLElementSizeHandle.ts: -------------------------------------------------------------------------------- 1 | import type { ResizeableRenderer } from "../../framework/src/ResizeableRenderer.interface"; 2 | 3 | export function HTMLElementSizeHandle(htmlElement: HTMLElement): ResizeableRenderer { 4 | function setSize(width: number, height: number): void { 5 | htmlElement.style.width = `${width}px`; 6 | htmlElement.style.height = `${height}px`; 7 | } 8 | 9 | return Object.freeze({ 10 | setSize: setSize, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/dom/src/MessageFontPreload.type.ts: -------------------------------------------------------------------------------- 1 | import type { FontPreloadParameters } from "./FontPreloadParameters.type"; 2 | import type { RPCMessage } from "../../framework/src/RPCMessage.type"; 3 | 4 | export type MessageFontPreload = { 5 | preloadFont: FontPreloadParameters & RPCMessage; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/dom/src/WindowFocusObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | import type { WindowFocusObserverState } from "./WindowFocusObserverState.type"; 5 | 6 | export interface WindowFocusObserver extends MainLoopUpdatable, Service { 7 | readonly state: WindowFocusObserverState; 8 | readonly isWindowFocusObserver: true; 9 | } 10 | -------------------------------------------------------------------------------- /packages/dom/src/WindowFocusObserverState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | 3 | export type WindowFocusObserverState = MainLoopUpdatableState & { 4 | isDocumentFocused: boolean; 5 | isFocusChanged: boolean; 6 | lastUpdate: number; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/dom/src/WindowResizeObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | export interface WindowResizeObserver extends MainLoopUpdatable, Service { 5 | isWindowResizeObserver: true; 6 | } 7 | -------------------------------------------------------------------------------- /packages/dom/src/clearHTMLElement.ts: -------------------------------------------------------------------------------- 1 | export function clearHTMLElement(htmlElement: HTMLElement): void { 2 | while (htmlElement.lastChild) { 3 | htmlElement.removeChild(htmlElement.lastChild); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/dom/src/createConstructableStyleSheetsCache.ts: -------------------------------------------------------------------------------- 1 | import type { ConstructableStyleSheetsCache } from "./ConstructableStyleSheetsCache.type"; 2 | 3 | export function createConstructableStyleSheetsCache(): ConstructableStyleSheetsCache { 4 | // Might be something else potentially. Currently it's good enough, but 5 | // making changes from just plain object would not require changes in the 6 | // code that uses this package. 7 | return {}; 8 | } 9 | -------------------------------------------------------------------------------- /packages/dom/src/createConstructableStylesheet.ts: -------------------------------------------------------------------------------- 1 | import { createConstructableStyleSheetsCache } from "./createConstructableStyleSheetsCache"; 2 | 3 | import type { ConstructableStyleSheetsCache } from "./ConstructableStyleSheetsCache.type"; 4 | 5 | const _styleSheetsCache: ConstructableStyleSheetsCache = createConstructableStyleSheetsCache(); 6 | 7 | export function createConstructableStylesheet(css: string): CSSStyleSheet { 8 | if (_styleSheetsCache.hasOwnProperty(css)) { 9 | return _styleSheetsCache[css]; 10 | } 11 | 12 | const styleSheet = new globalThis.CSSStyleSheet(); 13 | 14 | // @ts-ignore this is a Chrome only feature at this point and as such it is 15 | // not typed 16 | styleSheet.replaceSync(css); 17 | 18 | _styleSheetsCache[css] = styleSheet; 19 | 20 | return styleSheet; 21 | } 22 | -------------------------------------------------------------------------------- /packages/dom/src/getHTMLElementById.test.ts: -------------------------------------------------------------------------------- 1 | import { JSDOM } from "jsdom"; 2 | 3 | import { getHTMLElementById } from "./getHTMLElementById"; 4 | 5 | test("finds dom element", function () { 6 | const dom = new JSDOM(`
Hello
`); 7 | const element = getHTMLElementById(dom.window.document, "test"); 8 | 9 | expect(element.textContent).toBe("Hello"); 10 | }); 11 | 12 | test("throws when document is not found", function () { 13 | const dom = new JSDOM(`
Hello
`); 14 | 15 | expect(function () { 16 | getHTMLElementById(dom.window.document, "foo"); 17 | }).toThrow(); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/dom/src/getHTMLElementById.ts: -------------------------------------------------------------------------------- 1 | export function getHTMLElementById(documentElement: Document | ShadowRoot, id: string): HTMLElement { 2 | const element = documentElement.getElementById(id); 3 | 4 | if (!element) { 5 | throw new Error(`Element does not exist: "#${id}"`); 6 | } 7 | 8 | return element; 9 | } 10 | -------------------------------------------------------------------------------- /packages/dom/src/preloadImage.ts: -------------------------------------------------------------------------------- 1 | import type { Progress } from "../../framework/src/Progress.interface"; 2 | 3 | export function preloadImage(progress: Progress, textureUrl: string): Promise { 4 | return new Promise(function (resolve, reject) { 5 | const image = new Image(); 6 | let resolved: boolean = false; 7 | 8 | image.crossOrigin = "Anonymous"; 9 | image.onerror = reject; 10 | image.onload = function () { 11 | if (resolved) { 12 | return; 13 | } 14 | 15 | resolved = true; 16 | resolve(image); 17 | }; 18 | 19 | image.src = textureUrl; 20 | 21 | if (image.complete) { 22 | resolved = true; 23 | resolve(image); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /packages/dynamics/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/dynamics/README.md: -------------------------------------------------------------------------------- 1 | # `dynamics` 2 | 3 | Package that handles physics. 4 | -------------------------------------------------------------------------------- /packages/dynamics/src/DynamicsWorld.interface.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 4 | import type { Pauseable } from "../../framework/src/Pauseable.interface"; 5 | import type { Service } from "../../framework/src/Service.interface"; 6 | 7 | import type { DynamicsWorldInfo } from "./DynamicsWorldInfo.type"; 8 | import type { DynamicsWorldState } from "./DynamicsWorldState.type"; 9 | import type { SimulantsLookup } from "./SimulantsLookup.type"; 10 | 11 | export interface DynamicsWorld extends MainLoopUpdatable, Pauseable, Service { 12 | readonly info: DynamicsWorldInfo; 13 | readonly isDynamicsWorld: true; 14 | readonly state: DynamicsWorldState; 15 | } 16 | -------------------------------------------------------------------------------- /packages/dynamics/src/DynamicsWorldInfo.type.ts: -------------------------------------------------------------------------------- 1 | export type DynamicsWorldInfo = { 2 | registeredSimulants: number; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/dynamics/src/DynamicsWorldState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | import type { PauseableState } from "../../framework/src/PauseableState.type"; 3 | 4 | export type DynamicsWorldState = MainLoopUpdatableState & PauseableState; 5 | -------------------------------------------------------------------------------- /packages/dynamics/src/MessageFeedbackSimulantPreloaded.type.ts: -------------------------------------------------------------------------------- 1 | import type { SimulantsLookup } from "./SimulantsLookup.type"; 2 | 3 | export type MessageFeedbackSimulantPreloaded = { 4 | id: string; 5 | simulant: K; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/dynamics/src/MessageSimulantDispose.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageSimulantDispose = Array; 2 | -------------------------------------------------------------------------------- /packages/dynamics/src/MessageSimulantRegister.type.ts: -------------------------------------------------------------------------------- 1 | import type { SimulantsLookup } from "./SimulantsLookup.type"; 2 | 3 | export type MessageSimulantRegister = { 4 | id: string; 5 | simulant: K; 6 | simulantFeedbackMessagePort: MessagePort; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/dynamics/src/MessageSimulantUpdate.type.ts: -------------------------------------------------------------------------------- 1 | import type { SimulantsLookup } from "./SimulantsLookup.type"; 2 | 3 | export type MessageSimulantUpdate = { 4 | id: string; 5 | simulant: string & keyof L; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/dynamics/src/RigidBodyRemoteHandle.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3Simple } from "../../quakemaps/src/Vector3Simple.type"; 2 | 3 | export interface RigidBodyRemoteHandle { 4 | applyCentralForce(vector3: Vector3Simple): void; 5 | 6 | applyCentralImpulse(vector3: Vector3Simple): void; 7 | 8 | setLinearVelocity(vector3: Vector3Simple): void; 9 | } 10 | -------------------------------------------------------------------------------- /packages/dynamics/src/Simulant.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable } from "../../framework/src/Disposable.interface"; 2 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 3 | import type { Mountable } from "../../framework/src/Mountable.interface"; 4 | import type { Pauseable } from "../../framework/src/Pauseable.interface"; 5 | import type { Preloadable } from "../../framework/src/Preloadable.interface"; 6 | 7 | import type { SimulantState } from "./SimulantState.type"; 8 | 9 | export interface Simulant extends Disposable, MainLoopUpdatable, Mountable, Pauseable, Preloadable { 10 | readonly isSimulant: true; 11 | readonly state: SimulantState; 12 | } 13 | -------------------------------------------------------------------------------- /packages/dynamics/src/SimulantFactory.interface.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import type { MessageSimulantRegister } from "./MessageSimulantRegister.type"; 4 | import type { SimulantsLookup } from "./SimulantsLookup.type"; 5 | import type { UserDataRegistry } from "./UserDataRegistry.interface"; 6 | 7 | export interface SimulantFactory { 8 | readonly isSimulantFactory: true; 9 | 10 | create( 11 | ammo: typeof Ammo, 12 | dynamicsWorld: Ammo.btDiscreteDynamicsWorld, 13 | userDataRegistry: UserDataRegistry, 14 | request: MessageSimulantRegister 15 | ): S[K]; 16 | } 17 | -------------------------------------------------------------------------------- /packages/dynamics/src/SimulantState.type.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableState } from "../../framework/src/DisposableState.type"; 2 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 3 | import type { MountableState } from "../../framework/src/MountableState.type"; 4 | import type { PauseableState } from "../../framework/src/PauseableState.type"; 5 | import type { PreloadableState } from "../../framework/src/PreloadableState.type"; 6 | 7 | export type SimulantState = DisposableState & 8 | MainLoopUpdatableState & 9 | MountableState & 10 | PauseableState & 11 | PreloadableState; 12 | -------------------------------------------------------------------------------- /packages/dynamics/src/SimulantsLookup.type.ts: -------------------------------------------------------------------------------- 1 | import type { Simulant } from "./Simulant.interface"; 2 | 3 | export type SimulantsLookup = { 4 | [key: string]: Simulant; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/dynamics/src/UserDataHandle.interface.ts: -------------------------------------------------------------------------------- 1 | export interface UserDataHandle { 2 | readonly data: Map; 3 | readonly isUserDataHandle: true; 4 | readonly simulantId: string; 5 | readonly userIndex: number; 6 | } 7 | -------------------------------------------------------------------------------- /packages/dynamics/src/UserDataHandle.ts: -------------------------------------------------------------------------------- 1 | import { UserDataHandle as IUserDataHandle } from "./UserDataHandle.interface"; 2 | 3 | export function UserDataHandle(id: string, userIndex: number): IUserDataHandle { 4 | return Object.freeze({ 5 | data: new Map(), 6 | isUserDataHandle: true, 7 | simulantId: id, 8 | userIndex: userIndex, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/dynamics/src/UserDataRegistry.interface.ts: -------------------------------------------------------------------------------- 1 | import type { UserDataHandle } from "./UserDataHandle.interface"; 2 | 3 | export interface UserDataRegistry { 4 | readonly isUserDataRegistry: true; 5 | 6 | createHandleById(id: string): UserDataHandle; 7 | 8 | disposeHandleById(id: string): void; 9 | 10 | getHandleById(id: string): UserDataHandle; 11 | 12 | getHandleByUserIndex(userIndex: number): UserDataHandle; 13 | 14 | getIdByUserIndex(userIndex: number): string; 15 | 16 | getUserIndexById(id: string): number; 17 | 18 | hasIdByUserIndex(userIndex: number): boolean; 19 | } 20 | -------------------------------------------------------------------------------- /packages/dynamics/src/UserDataRegistry.test.ts: -------------------------------------------------------------------------------- 1 | import { UserDataRegistry } from "./UserDataRegistry"; 2 | 3 | test("user data is set", function () { 4 | const userDataRegistry = UserDataRegistry(); 5 | 6 | const handle = userDataRegistry.createHandleById("test"); 7 | 8 | handle.data.set("foo", "bar"); 9 | 10 | expect(handle.userIndex).toBe(1); 11 | 12 | expect(userDataRegistry.hasIdByUserIndex(1)).toBe(true); 13 | expect(userDataRegistry.getIdByUserIndex(1)).toBe("test"); 14 | 15 | expect(userDataRegistry.getHandleById("test")).toBe(handle); 16 | expect(userDataRegistry.getHandleByUserIndex(1)).toBe(handle); 17 | 18 | expect(userDataRegistry.getHandleById("test").data.get("foo")).toBe("bar"); 19 | 20 | userDataRegistry.disposeHandleById("test"); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/expression-language/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/expression-language/README.md: -------------------------------------------------------------------------------- 1 | # `expression-language` 2 | -------------------------------------------------------------------------------- /packages/expression-language/src/Evaluator.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Evaluator { 2 | evaluate(expression: string): Promise; 3 | 4 | evaluateSync(expression: string): T; 5 | } 6 | -------------------------------------------------------------------------------- /packages/expression-language/src/Evaluator.test.ts: -------------------------------------------------------------------------------- 1 | import { Evaluator } from "./Evaluator"; 2 | 3 | test("evaluates expressions without context", function () { 4 | const evaluator = Evaluator(); 5 | 6 | const ret = evaluator.evaluate("2 + 3"); 7 | 8 | return expect(ret).resolves.toBe(5); 9 | }); 10 | 11 | test("evaluates expressions with context", function () { 12 | const evaluator = Evaluator({ 13 | foo: 7, 14 | }); 15 | 16 | const ret = evaluator.evaluate("2 + foo"); 17 | 18 | return expect(ret).resolves.toBe(9); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/expression-language/src/Evaluator.ts: -------------------------------------------------------------------------------- 1 | import jexl from "jexl"; 2 | 3 | import type { Evaluator as IEvaluator } from "./Evaluator.interface"; 4 | import type { EvaluatorContext } from "./EvaluatorContext.type"; 5 | 6 | export function Evaluator(context: EvaluatorContext = {}): IEvaluator { 7 | function evaluate(expression: string): Promise { 8 | return jexl.eval(expression, context); 9 | } 10 | 11 | function evaluateSync(expression: string): T { 12 | try { 13 | return jexl.evalSync(expression, context); 14 | } catch (e) { 15 | throw new Error(`Error while parsing expression "${expression}": "${JSON.stringify(e)}"`); 16 | } 17 | } 18 | 19 | return Object.freeze({ 20 | evaluate: evaluate, 21 | evaluateSync: evaluateSync, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /packages/expression-language/src/EvaluatorContext.type.ts: -------------------------------------------------------------------------------- 1 | export type EvaluatorContext = { 2 | [key: string]: any; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/framework/README.md: -------------------------------------------------------------------------------- 1 | # `framework` 2 | 3 | Base interfaces and services other packages rely on. 4 | -------------------------------------------------------------------------------- /packages/framework/src/ArgumentCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type ArgumentCallback = (arg: T) => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/CameraControllerState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "./MainLoopUpdatableState.type"; 2 | import type { MountableState } from "./MountableState.type"; 3 | import type { PauseableState } from "./PauseableState.type"; 4 | 5 | export type CameraControllerState = MainLoopUpdatableState & 6 | MountableState & 7 | PauseableState & { 8 | lastCameraTypeChange: number; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/framework/src/DimensionsIndices.enum.ts: -------------------------------------------------------------------------------- 1 | export const enum DimensionsIndices { 2 | LAST_UPDATE, 3 | 4 | D_HEIGHT, 5 | D_WIDTH, 6 | P_BOTTOM, 7 | P_LEFT, 8 | P_RIGHT, 9 | P_TOP, 10 | 11 | // This one NEEDS to be last to get the enum size correctly 12 | __TOTAL, 13 | } 14 | -------------------------------------------------------------------------------- /packages/framework/src/DimensionsState.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsIndices } from "./DimensionsIndices.enum"; 2 | 3 | function createEmptyState(usesSharedBuffer: boolean): Uint32Array { 4 | if (usesSharedBuffer) { 5 | return new Uint32Array(new SharedArrayBuffer(DimensionsIndices.__TOTAL * Uint32Array.BYTES_PER_ELEMENT)); 6 | } 7 | 8 | return new Uint32Array(DimensionsIndices.__TOTAL); 9 | } 10 | 11 | export const DimensionsState = Object.freeze({ 12 | createEmptyState: createEmptyState, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/framework/src/Director.interface.ts: -------------------------------------------------------------------------------- 1 | import type { DirectorState } from "./DirectorState.type"; 2 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 3 | import type { Service } from "./Service.interface"; 4 | 5 | export interface Director extends MainLoopUpdatable, Service { 6 | readonly state: DirectorState; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/DirectorPollablePreloadingObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 2 | import type { Service } from "./Service.interface"; 3 | 4 | export interface DirectorPollablePreloadingObserver extends MainLoopUpdatable, Service { 5 | readonly isDirectorPollablePreloadingObserver: true; 6 | } 7 | -------------------------------------------------------------------------------- /packages/framework/src/DirectorState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "./MainLoopUpdatableState.type"; 2 | import type { Scene } from "./Scene.interface"; 3 | 4 | export type DirectorState = MainLoopUpdatableState & { 5 | current: null | Scene; 6 | isStarted: boolean; 7 | isTransitioning: boolean; 8 | lastUpdateCurrentTick: number; 9 | lastUpdateNextTick: number; 10 | lastUpdateTransitioningTick: number; 11 | next: null | Scene; 12 | transitioning: null | Scene; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/framework/src/Disposable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableGeneric } from "./DisposableGeneric.interface"; 2 | import type { DisposableState } from "./DisposableState.type"; 3 | import type { Nameable } from "./Nameable.interface"; 4 | 5 | export interface Disposable extends DisposableGeneric, Nameable { 6 | readonly isDisposable: true; 7 | readonly state: DisposableState; 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/DisposableCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type DisposableCallback = () => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/DisposableGeneric.interface.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableCallback } from "./DisposableCallback.type"; 2 | 3 | export interface DisposableGeneric { 4 | readonly dispose: DisposableCallback; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/DisposableState.type.ts: -------------------------------------------------------------------------------- 1 | export type DisposableState = { 2 | isDisposed: boolean; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/src/EventBus.interface.ts: -------------------------------------------------------------------------------- 1 | import type { EventBusZoomAmountCallback } from "./EventBusZoomAmountCallback.type"; 2 | 3 | export interface EventBus { 4 | readonly POINTER_ZOOM_REQUEST: Set; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/EventBus.ts: -------------------------------------------------------------------------------- 1 | import type { EventBus } from "./EventBus.interface"; 2 | import type { EventBusZoomAmountCallback } from "./EventBusZoomAmountCallback.type"; 3 | 4 | export function EventBus(): EventBus { 5 | return Object.freeze({ 6 | POINTER_ZOOM_REQUEST: new Set(), 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/EventBusZoomAmountCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type EventBusZoomAmountCallback = (zoomAmount: number) => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/FallbackScheduler.ts: -------------------------------------------------------------------------------- 1 | import { isRequestAnimationFrameSupported } from "./isRequestAnimationFrameSupported"; 2 | import { RequestAnimationFrameScheduler } from "./RequestAnimationFrameScheduler"; 3 | import { SetTimeoutScheduler } from "./SetTimeoutScheduler"; 4 | 5 | import type { Scheduler } from "./Scheduler.interface"; 6 | 7 | type TickType = ReturnType; 8 | 9 | export function FallbackScheduler(timestep: number = 1000 / 60): Scheduler { 10 | if (isRequestAnimationFrameSupported()) { 11 | return RequestAnimationFrameScheduler(); 12 | } 13 | 14 | return SetTimeoutScheduler(timestep); 15 | } 16 | -------------------------------------------------------------------------------- /packages/framework/src/GenericCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type GenericCallback = () => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/GeometryAttributes.type.ts: -------------------------------------------------------------------------------- 1 | export type GeometryAttributes = { 2 | index: null | Uint16Array; 3 | normal: Float32Array; 4 | position: Float32Array; 5 | transferables: Array; 6 | uv: Float32Array; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/framework/src/Identifiable.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Identifiable { 2 | id: string; 3 | } 4 | -------------------------------------------------------------------------------- /packages/framework/src/IsUserSettingsValidCallback.type.ts: -------------------------------------------------------------------------------- 1 | import type { UserSettings } from "./UserSettings.type"; 2 | 3 | export type IsUserSettingsValidCallback = (userSettings: any) => userSettings is U; 4 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoop.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopTicker } from "./MainLoopTicker.interface"; 2 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 3 | import type { Scheduler } from "./Scheduler.interface"; 4 | import type { Service } from "./Service.interface"; 5 | 6 | export interface MainLoop extends Service { 7 | isMainLoop: true; 8 | scheduler: Scheduler; 9 | ticker: MainLoopTicker; 10 | updatables: Set; 11 | 12 | tick(now: number): void; 13 | } 14 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoop.test.ts: -------------------------------------------------------------------------------- 1 | import Loglevel from "loglevel"; 2 | 3 | import { MainLoop } from "./MainLoop"; 4 | import { SetTimeoutScheduler } from "./SetTimeoutScheduler"; 5 | 6 | test("loops until canceled", async function () { 7 | const logger = Loglevel.getLogger("test"); 8 | const mainLoop = MainLoop(logger, SetTimeoutScheduler()); 9 | 10 | let ticks = 0; 11 | 12 | await new Promise(function (resolve) { 13 | mainLoop.updatables.add({ 14 | state: { 15 | needsUpdates: true, 16 | }, 17 | 18 | update(delta: number): void { 19 | ticks += 1; 20 | 21 | if (ticks === 5) { 22 | mainLoop.stop(); 23 | resolve(); 24 | } 25 | }, 26 | }); 27 | mainLoop.start(); 28 | }); 29 | 30 | expect(ticks).toBe(5); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoopStatsHook.interface.ts: -------------------------------------------------------------------------------- 1 | import { StatsHook } from "./StatsHook.interface"; 2 | 3 | export interface MainLoopStatsHook extends StatsHook { 4 | readonly isMainLoopStatsHook: true; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoopStatsReport.type.ts: -------------------------------------------------------------------------------- 1 | import type { StatsReport } from "./StatsReport.type"; 2 | 3 | export type MainLoopStatsReport = StatsReport & { 4 | currentInterval: number; 5 | currentIntervalDuration: number; 6 | currentIntervalTicks: number; 7 | schedulerName: string; 8 | tickerName: string; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoopTicker.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopTickerState } from "./MainLoopTickerState.type"; 2 | import type { Nameable } from "./Nameable.interface"; 3 | import type { TickTimerState } from "./TickTimerState.type"; 4 | 5 | export interface MainLoopTicker extends Nameable { 6 | state: MainLoopTickerState; 7 | tickTimerState: TickTimerState; 8 | 9 | tick(delta: number, elapsedTime: number): void; 10 | } 11 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoopTickerState.type.ts: -------------------------------------------------------------------------------- 1 | export type MainLoopTickerState = { 2 | scheduledUpdates: number; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoopUpdatable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "./MainLoopUpdatableState.type"; 2 | import type { MainLoopUpdateCallback } from "./MainLoopUpdateCallback.type"; 3 | 4 | export interface MainLoopUpdatable { 5 | readonly state: MainLoopUpdatableState; 6 | readonly update: MainLoopUpdateCallback; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoopUpdatableState.type.ts: -------------------------------------------------------------------------------- 1 | export type MainLoopUpdatableState = { 2 | // Can be set to false to be permanently removed from the loop. 3 | needsUpdates: boolean; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/framework/src/MainLoopUpdateCallback.type.ts: -------------------------------------------------------------------------------- 1 | import type { TickTimerState } from "./TickTimerState.type"; 2 | 3 | export type MainLoopUpdateCallback = (delta: number, elapsedTime: number, tickTimerState: TickTimerState) => void; 4 | -------------------------------------------------------------------------------- /packages/framework/src/MessageEventData.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageEventData = { 2 | [key: string]: any; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/src/MessageEventHandler.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageEventHandler = (evt: ExtendableMessageEvent | MessageEvent) => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/MessageEventMultiRouter.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageEventMultiRouter = { 2 | [key: string]: ( 3 | messagePort: MessagePort, 4 | message: any, 5 | originalEvent: Message 6 | ) => void; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/framework/src/MessageEventRouter.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageEventRouter = { 2 | [key: string]: ( 3 | message: any, 4 | originalEvent: Message 5 | ) => void; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/framework/src/MessageProgress.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageProgress = { 2 | id: string; 3 | type: string; 4 | uri: string; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/framework/src/MessageProgressChange.type.ts: -------------------------------------------------------------------------------- 1 | import type { MessageProgress } from "./MessageProgress.type"; 2 | 3 | export type MessageProgressChange = MessageProgress & { 4 | loaded: number; 5 | total: number; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/framework/src/MessageProgressDone.type.ts: -------------------------------------------------------------------------------- 1 | import type { MessageProgressChange } from "./MessageProgressChange.type"; 2 | 3 | export type MessageProgressDone = MessageProgressChange; 4 | -------------------------------------------------------------------------------- /packages/framework/src/MessageProgressError.type.ts: -------------------------------------------------------------------------------- 1 | import type { MessageProgress } from "./MessageProgress.type"; 2 | 3 | export type MessageProgressError = MessageProgress & { 4 | message: string; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/framework/src/MessageProgressExpect.type.ts: -------------------------------------------------------------------------------- 1 | import type { MessageProgress } from "./MessageProgress.type"; 2 | 3 | export type MessageProgressExpect = MessageProgress & { 4 | expect: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/framework/src/MessageProgressStart.type.ts: -------------------------------------------------------------------------------- 1 | import type { MessageProgress } from "./MessageProgress.type"; 2 | 3 | export type MessageProgressStart = MessageProgress; 4 | -------------------------------------------------------------------------------- /packages/framework/src/MessageWorkerReady.type.ts: -------------------------------------------------------------------------------- 1 | export type MessageWorkerReady = { 2 | ready: true; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/src/Mountable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MountableCallback } from "./MountableCallback.type"; 2 | import type { MountableState } from "./MountableState.type"; 3 | import type { Nameable } from "./Nameable.interface"; 4 | import type { UnmountableCallback } from "./UnmountableCallback.type"; 5 | 6 | export interface Mountable extends Nameable { 7 | readonly isMountable: true; 8 | readonly mount: MountableCallback; 9 | readonly state: MountableState; 10 | readonly unmount: UnmountableCallback; 11 | } 12 | -------------------------------------------------------------------------------- /packages/framework/src/MountableCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type MountableCallback = () => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/MountableState.type.ts: -------------------------------------------------------------------------------- 1 | export type MountableState = { 2 | isMounted: boolean; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/src/Nameable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Identifiable } from "./Identifiable.interface"; 2 | 3 | export interface Nameable extends Identifiable { 4 | name: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/Pauseable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Nameable } from "./Nameable.interface"; 2 | import type { PauseableState } from "./PauseableState.type"; 3 | 4 | export interface Pauseable extends Nameable { 5 | state: PauseableState; 6 | 7 | pause(): void; 8 | 9 | unpause(): void; 10 | } 11 | -------------------------------------------------------------------------------- /packages/framework/src/PauseableState.type.ts: -------------------------------------------------------------------------------- 1 | export type PauseableState = { 2 | isPaused: boolean; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/src/PerformanceMemory.type.ts: -------------------------------------------------------------------------------- 1 | export type PerformanceMemory = { 2 | jsHeapSizeLimit: number; 3 | totalJSHeapSize: number; 4 | usedJSHeapSize: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/framework/src/PerformanceStatsReport.type.ts: -------------------------------------------------------------------------------- 1 | import type { PerformanceMemory } from "./PerformanceMemory.type"; 2 | import type { StatsReport } from "./StatsReport.type"; 3 | 4 | export type PerformanceStatsReport = StatsReport & PerformanceMemory; 5 | -------------------------------------------------------------------------------- /packages/framework/src/PollablePreloading.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdateCallback } from "./MainLoopUpdateCallback.type"; 2 | 3 | export interface PollablePreloading { 4 | readonly isPollablePreloading: true; 5 | 6 | updatePreloadingState: MainLoopUpdateCallback; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/Preloadable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Nameable } from "./Nameable.interface"; 2 | import type { PreloadableCallback } from "./PreloadableCallback.type"; 3 | import type { PreloadableState } from "./PreloadableState.type"; 4 | 5 | export interface Preloadable extends Nameable { 6 | readonly isPreloadable: true; 7 | readonly preload: PreloadableCallback; 8 | readonly state: PreloadableState; 9 | } 10 | -------------------------------------------------------------------------------- /packages/framework/src/PreloadableCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type PreloadableCallback = () => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/PreloadableState.type.ts: -------------------------------------------------------------------------------- 1 | export type PreloadableState = { 2 | isPreloaded: boolean; 3 | isPreloading: boolean; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/framework/src/Preloader.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 2 | 3 | export interface Preloader extends MainLoopUpdatable { 4 | readonly isPreloader: true; 5 | 6 | wait(): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/Progress.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Nameable } from "./Nameable.interface"; 2 | import type { ProgressCallback } from "./ProgressCallback.type"; 3 | import type { ProgressState } from "./ProgressState.type"; 4 | 5 | export interface Progress extends Nameable { 6 | readonly state: ProgressState; 7 | 8 | readonly progress: ProgressCallback; 9 | 10 | done(): void; 11 | 12 | error(err: Error): void; 13 | 14 | start(): void; 15 | 16 | wait(promise: Promise): Promise; 17 | } 18 | -------------------------------------------------------------------------------- /packages/framework/src/ProgressCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type ProgressCallback = (downloaded: number, total: number) => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/ProgressManager.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MessageProgressChange } from "./MessageProgressChange.type"; 2 | import type { MessageProgressDone } from "./MessageProgressDone.type"; 3 | import type { MessageProgressError } from "./MessageProgressError.type"; 4 | import type { MessageProgressStart } from "./MessageProgressStart.type"; 5 | import type { ProgressManagerState } from "./ProgressManagerState.type"; 6 | 7 | export interface ProgressManager { 8 | state: ProgressManagerState; 9 | 10 | done(message: MessageProgressDone): void; 11 | 12 | error(message: MessageProgressError): void; 13 | 14 | expect(expect: number): void; 15 | 16 | progress(message: MessageProgressChange): void; 17 | 18 | reset(): void; 19 | 20 | start(message: MessageProgressStart): void; 21 | } 22 | -------------------------------------------------------------------------------- /packages/framework/src/ProgressManagerState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MessageProgressChange } from "./MessageProgressChange.type"; 2 | import type { MessageProgressError } from "./MessageProgressError.type"; 3 | 4 | export type ProgressManagerState = { 5 | errors: ReadonlyArray; 6 | expect: number; 7 | messages: ReadonlyArray; 8 | version: number; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/framework/src/ProgressState.type.ts: -------------------------------------------------------------------------------- 1 | export type ProgressState = { 2 | isFailed: boolean; 3 | isFinished: boolean; 4 | isStarted: boolean; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/framework/src/RPCLookupTable.type.ts: -------------------------------------------------------------------------------- 1 | export type RPCLookupTable = { 2 | [key: string]: Function; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/framework/src/RPCMessage.type.ts: -------------------------------------------------------------------------------- 1 | export type RPCMessage = { 2 | rpc: string; 3 | [key: string]: any; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/framework/src/RPCResponseHandler.type.ts: -------------------------------------------------------------------------------- 1 | import type { RPCMessage } from "./RPCMessage.type"; 2 | 3 | export type RPCResponseHandler = (data: ResponseData) => MappedValue; 4 | -------------------------------------------------------------------------------- /packages/framework/src/RegistersMessagePort.interface.ts: -------------------------------------------------------------------------------- 1 | export interface RegistersMessagePort { 2 | thread: "main" | "worker"; 3 | 4 | registerMessagePort(messagePort: MessagePort): void; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/RequestAnimationFrameScheduler.ts: -------------------------------------------------------------------------------- 1 | import { generateUUID } from "../../math/src/generateUUID"; 2 | 3 | import type { Scheduler } from "./Scheduler.interface"; 4 | import type { SchedulerCallback } from "./SchedulerCallback.type"; 5 | 6 | type TickType = ReturnType; 7 | 8 | function cancelFrame(frameId: TickType): void { 9 | cancelAnimationFrame(frameId); 10 | } 11 | 12 | function requestFrame(callback: SchedulerCallback): TickType { 13 | return requestAnimationFrame(callback); 14 | } 15 | 16 | export function RequestAnimationFrameScheduler(): Scheduler { 17 | return Object.freeze({ 18 | id: generateUUID(), 19 | name: "RequestAnimationFrameScheduler", 20 | 21 | cancelFrame: cancelFrame, 22 | requestFrame: requestFrame, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /packages/framework/src/ResizeableRenderer.interface.ts: -------------------------------------------------------------------------------- 1 | export interface ResizeableRenderer { 2 | setSize(width: number, height: number, updateStyle?: boolean): void; 3 | } 4 | -------------------------------------------------------------------------------- /packages/framework/src/ReusedResponse.type.ts: -------------------------------------------------------------------------------- 1 | export type ReusedResponse = { 2 | data: T; 3 | isLast: boolean; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/framework/src/ReusedResponsesCache.type.ts: -------------------------------------------------------------------------------- 1 | export type ReusedResponsesCache = Map>; 2 | -------------------------------------------------------------------------------- /packages/framework/src/ReusedResponsesUsage.type.ts: -------------------------------------------------------------------------------- 1 | export type ReusedResponsesUsage = Map; 2 | -------------------------------------------------------------------------------- /packages/framework/src/Scene.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable } from "./Disposable.interface"; 2 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 3 | import type { Mountable } from "./Mountable.interface"; 4 | import type { Pauseable } from "./Pauseable.interface"; 5 | import type { Preloadable } from "./Preloadable.interface"; 6 | import type { SceneState } from "./SceneState.type"; 7 | 8 | export interface Scene extends Disposable, MainLoopUpdatable, Mountable, Pauseable, Preloadable { 9 | readonly isScene: true; 10 | readonly state: SceneState; 11 | } 12 | -------------------------------------------------------------------------------- /packages/framework/src/SceneState.type.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableState } from "./DisposableState.type"; 2 | import type { MainLoopUpdatableState } from "./MainLoopUpdatableState.type"; 3 | import type { MountableState } from "./MountableState.type"; 4 | import type { PauseableState } from "./PauseableState.type"; 5 | import type { PreloadableState } from "./PreloadableState.type"; 6 | 7 | export type SceneState = DisposableState & MainLoopUpdatableState & MountableState & PauseableState & PreloadableState; 8 | -------------------------------------------------------------------------------- /packages/framework/src/SceneTransition.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 2 | import type { SceneTransitionState } from "./SceneTransitionState.type"; 3 | import type { Service } from "./Service.interface"; 4 | 5 | export interface SceneTransition extends MainLoopUpdatable, Service { 6 | readonly state: SceneTransitionState; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/SceneTransitionState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "./MainLoopUpdatableState.type"; 2 | 3 | export type SceneTransitionState = MainLoopUpdatableState & { 4 | lastUpdateCurrentTick: number; 5 | lastUpdateNextTick: number; 6 | lastUpdateTransitioningTick: number; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/framework/src/Scheduler.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Nameable } from "./Nameable.interface"; 2 | import type { SchedulerCallback } from "./SchedulerCallback.type"; 3 | 4 | export interface Scheduler extends Nameable { 5 | cancelFrame(frameId: T): void; 6 | 7 | requestFrame(callback: SchedulerCallback): T; 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/SchedulerCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type SchedulerCallback = (timestamp: number) => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/Service.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Nameable } from "./Nameable.interface"; 2 | 3 | export interface Service extends Nameable { 4 | start(): void; 5 | 6 | stop(): void; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/ServiceBuilder.interface.ts: -------------------------------------------------------------------------------- 1 | import type { ArgumentCallback } from "./ArgumentCallback.type"; 2 | import type { Nameable } from "./Nameable.interface"; 3 | 4 | export interface ServiceBuilder extends Nameable { 5 | readonly dependencies: Partial; 6 | readonly onready: Set>; 7 | 8 | isReady(): boolean; 9 | 10 | setDependency(key: K, dependency: D[K]): void; 11 | } 12 | -------------------------------------------------------------------------------- /packages/framework/src/ServiceManager.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 2 | import type { Service } from "./Service.interface"; 3 | 4 | export interface ServiceManager extends MainLoopUpdatable, Service { 5 | readonly services: Set; 6 | } 7 | -------------------------------------------------------------------------------- /packages/framework/src/StatsHook.interface.ts: -------------------------------------------------------------------------------- 1 | import { Nameable } from "./Nameable.interface"; 2 | import { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 3 | import { StatsReport } from "./StatsReport.type"; 4 | 5 | export interface StatsHook extends MainLoopUpdatable, Nameable { 6 | readonly isStatsHook: true; 7 | readonly statsReport: StatsReport; 8 | readonly statsReportIntervalSeconds: number; 9 | 10 | reset(): void; 11 | } 12 | -------------------------------------------------------------------------------- /packages/framework/src/StatsReport.type.ts: -------------------------------------------------------------------------------- 1 | export type StatsReport = { 2 | [key: string]: boolean | number | string | StatsReport; 3 | } & { 4 | debugName: string; 5 | lastUpdate: number; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/framework/src/StatsReporter.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 2 | import type { Service } from "./Service.interface"; 3 | import type { StatsHook } from "./StatsHook.interface"; 4 | 5 | export interface StatsReporter extends MainLoopUpdatable, Service { 6 | readonly hooks: Set; 7 | readonly isStatsReporter: true; 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/SupportChecker.type.ts: -------------------------------------------------------------------------------- 1 | export type SupportChecker = () => boolean | Promise; 2 | -------------------------------------------------------------------------------- /packages/framework/src/TickTimerState.type.ts: -------------------------------------------------------------------------------- 1 | export type TickTimerState = { 2 | currentTick: number; 3 | delta: number; 4 | elapsedTime: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/framework/src/UnmountableCallback.type.ts: -------------------------------------------------------------------------------- 1 | export type UnmountableCallback = () => void; 2 | -------------------------------------------------------------------------------- /packages/framework/src/UserSettings.ts: -------------------------------------------------------------------------------- 1 | import type { UserSettings as IUserSettings } from "./UserSettings.type"; 2 | 3 | function createEmptyState(): IUserSettings { 4 | return { 5 | showStatsReporter: false, 6 | version: 0, 7 | }; 8 | } 9 | 10 | export const UserSettings = Object.freeze({ 11 | createEmptyState: createEmptyState, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/framework/src/UserSettings.type.ts: -------------------------------------------------------------------------------- 1 | export type UserSettings = { 2 | showStatsReporter: boolean; 3 | version: number; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/framework/src/UserSettingsManager.interface.ts: -------------------------------------------------------------------------------- 1 | import type { UserSettingsManagerState } from "./UserSettingsManagerState.type"; 2 | 3 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 4 | import type { Preloadable } from "./Preloadable.interface"; 5 | 6 | export interface UserSettingsManager extends MainLoopUpdatable, Preloadable { 7 | readonly state: UserSettingsManagerState; 8 | 9 | isUserSettingsManager: true; 10 | } 11 | -------------------------------------------------------------------------------- /packages/framework/src/UserSettingsManagerState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "./MainLoopUpdatableState.type"; 2 | import type { PreloadableState } from "./PreloadableState.type"; 3 | 4 | export type UserSettingsManagerState = MainLoopUpdatableState & PreloadableState; 5 | -------------------------------------------------------------------------------- /packages/framework/src/UserSettingsSync.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 2 | import type { Service } from "./Service.interface"; 3 | 4 | export interface UserSettingsSync extends MainLoopUpdatable, Service { 5 | isUserSettingsSync: true; 6 | } 7 | -------------------------------------------------------------------------------- /packages/framework/src/WebGLRendererStatsReport.type.ts: -------------------------------------------------------------------------------- 1 | import type { StatsReport } from "./StatsReport.type"; 2 | 3 | export type WebGLRendererStatsReport = StatsReport & { 4 | memoryGeometries: number; 5 | memoryTextures: number; 6 | programs: number; 7 | renderCalls: number; 8 | renderLines: number; 9 | renderPoints: number; 10 | renderTriangles: number; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/framework/src/WorkerServiceClient.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "./MainLoopUpdatable.interface"; 2 | import type { Service } from "./Service.interface"; 3 | 4 | export interface WorkerServiceClient extends MainLoopUpdatable, Service { 5 | readonly isWorkerServiceClient: true; 6 | 7 | ready(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/attachMultiRouter.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { routeMultiMessage } from "./routeMultiMessage"; 4 | 5 | import type { MessageEventMultiRouter } from "./MessageEventMultiRouter.type"; 6 | 7 | export function attachMultiRouter(messagePort: MessagePort, router: MessageEventMultiRouter): void { 8 | messagePort.onmessage = function (evt: ExtendableMessageEvent | MessageEvent) { 9 | routeMultiMessage(messagePort, evt, evt.data, router); 10 | }; 11 | messagePort.start(); 12 | } 13 | -------------------------------------------------------------------------------- /packages/framework/src/createEmptyMesh.ts: -------------------------------------------------------------------------------- 1 | import { Mesh } from "three/src/objects/Mesh"; 2 | 3 | import { disposableGeneric } from "./disposableGeneric"; 4 | import { disposableMaterial } from "./disposableMaterial"; 5 | 6 | import type { Mesh as IMesh } from "three/src/objects/Mesh"; 7 | 8 | export function createEmptyMesh(): IMesh { 9 | const _mesh: IMesh = new Mesh(); 10 | 11 | disposableGeneric(_mesh.geometry)(); 12 | disposableMaterial(_mesh.material)(); 13 | 14 | return _mesh; 15 | } 16 | -------------------------------------------------------------------------------- /packages/framework/src/createMultiThreadMessageChannel.ts: -------------------------------------------------------------------------------- 1 | export function createMultiThreadMessageChannel(): MessageChannel { 2 | return new MessageChannel(); 3 | } 4 | -------------------------------------------------------------------------------- /packages/framework/src/createRPCLookupTable.ts: -------------------------------------------------------------------------------- 1 | import type { RPCLookupTable } from "./RPCLookupTable.type"; 2 | 3 | export function createRPCLookupTable(): RPCLookupTable { 4 | return {}; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/createReusedResponsesCache.ts: -------------------------------------------------------------------------------- 1 | import type { ReusedResponsesCache } from "./ReusedResponsesCache.type"; 2 | 3 | export function createReusedResponsesCache(): ReusedResponsesCache { 4 | return new Map(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/createReusedResponsesUsage.ts: -------------------------------------------------------------------------------- 1 | import type { ReusedResponsesUsage } from "./ReusedResponsesUsage.type"; 2 | 3 | export function createReusedResponsesUsage(): ReusedResponsesUsage { 4 | return new Map(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/createRouter.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { routeMessages } from "./routeMessages"; 4 | 5 | import type { MessageEventHandler } from "./MessageEventHandler.type"; 6 | import type { MessageEventRouter } from "./MessageEventRouter.type"; 7 | 8 | export function createRouter(router: MessageEventRouter): MessageEventHandler { 9 | return function (evt: ExtendableMessageEvent | MessageEvent): void { 10 | routeMessages(evt, evt.data, router); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/framework/src/createSettingsHandle.ts: -------------------------------------------------------------------------------- 1 | import type { GenericCallback } from "./GenericCallback.type"; 2 | 3 | import type { UserSettings } from "./UserSettings.type"; 4 | 5 | export function createSettingsHandle(userSettings: UserSettings, callback: GenericCallback): GenericCallback { 6 | let _lastAppliedVersion: number = -1; 7 | 8 | return function () { 9 | if (userSettings.version <= _lastAppliedVersion) { 10 | return; 11 | } 12 | 13 | _lastAppliedVersion = userSettings.version; 14 | callback(); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /packages/framework/src/createSingleThreadMessageChannel.ts: -------------------------------------------------------------------------------- 1 | import { SingleThreadMessageChannel } from "./SingleThreadMessageChannel"; 2 | 3 | export function createSingleThreadMessageChannel(): MessageChannel { 4 | return SingleThreadMessageChannel(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/disposableGeneric.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableCallback } from "./DisposableCallback.type"; 2 | import type { DisposableGeneric } from "./DisposableGeneric.interface"; 3 | 4 | export function disposableGeneric(generic: DisposableGeneric): DisposableCallback { 5 | return function () { 6 | generic.dispose(); 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/disposableMaterial.ts: -------------------------------------------------------------------------------- 1 | import { disposableGeneric } from "./disposableGeneric"; 2 | import { invoke } from "./invoke"; 3 | 4 | import type { Material } from "three"; 5 | 6 | import type { DisposableCallback } from "./DisposableCallback.type"; 7 | 8 | export function disposableMaterial(materials: Material | Array): DisposableCallback { 9 | if (!Array.isArray(materials)) { 10 | return disposableGeneric(materials); 11 | } 12 | 13 | const disposables = materials.map(disposableGeneric); 14 | 15 | return function () { 16 | disposables.forEach(invoke); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/framework/src/disposableMesh.ts: -------------------------------------------------------------------------------- 1 | import { disposableGeneric } from "./disposableGeneric"; 2 | import { disposableMaterial } from "./disposableMaterial"; 3 | 4 | import type { DisposableCallback } from "./DisposableCallback.type"; 5 | import type { Mesh as IMesh } from "three/src/objects/Mesh"; 6 | 7 | export function disposableMesh(mesh: IMesh): DisposableCallback { 8 | return function () { 9 | disposableGeneric(mesh.geometry)(); 10 | disposableMaterial(mesh.material)(); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/framework/src/disposeAll.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "./invoke"; 2 | 3 | import type { DisposableCallback } from "./DisposableCallback.type"; 4 | 5 | export function disposeAll(disposables: Set): void { 6 | disposables.forEach(invoke); 7 | disposables.clear(); 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/disposeWebGLRenderTarget.ts: -------------------------------------------------------------------------------- 1 | import type { WebGLRenderTarget } from "three/src/renderers/WebGLRenderTarget"; 2 | 3 | type SupportedRenederTargets = WebGLRenderTarget; 4 | 5 | function isWebGLRenderTarget(renderTarget: SupportedRenederTargets): renderTarget is WebGLRenderTarget { 6 | return true === (renderTarget as WebGLRenderTarget).isWebGLRenderTarget; 7 | } 8 | 9 | export function disposeWebGLRenderTarget(renderTarget: null | SupportedRenederTargets): void { 10 | if (null === renderTarget) { 11 | return; 12 | } 13 | 14 | if (isWebGLRenderTarget(renderTarget)) { 15 | renderTarget.dispose(); 16 | 17 | return; 18 | } 19 | 20 | throw new Error("Unsupported render target can't be disposed."); 21 | } 22 | -------------------------------------------------------------------------------- /packages/framework/src/findAllMeshes.ts: -------------------------------------------------------------------------------- 1 | import { isMesh } from "./isMesh"; 2 | 3 | import type { Mesh as IMesh } from "three/src/objects/Mesh"; 4 | import type { Object3D as IObject3D } from "three/src/core/Object3D"; 5 | 6 | export function findAllMeshes(object3d: IObject3D): Array { 7 | let meshes: Array = []; 8 | 9 | object3d.traverse(function (child: IObject3D) { 10 | if (!isMesh(child)) { 11 | return; 12 | } 13 | 14 | if (isMesh(child)) { 15 | meshes.push(child); 16 | } 17 | }); 18 | 19 | return meshes; 20 | } 21 | -------------------------------------------------------------------------------- /packages/framework/src/findMesh.ts: -------------------------------------------------------------------------------- 1 | import { isMesh } from "./isMesh"; 2 | 3 | import type { Mesh as IMesh } from "three/src/objects/Mesh"; 4 | import type { Object3D as IObject3D } from "three/src/core/Object3D"; 5 | 6 | export function findMesh(object3d: IObject3D): null | IMesh { 7 | let mesh: null | IMesh = null; 8 | 9 | object3d.traverse(function (child: IObject3D) { 10 | if (!isMesh(child)) { 11 | return; 12 | } 13 | 14 | if (isMesh(mesh)) { 15 | throw new Error("Found multiple meshes in Object3D."); 16 | } 17 | 18 | mesh = child; 19 | }); 20 | 21 | return mesh; 22 | } 23 | -------------------------------------------------------------------------------- /packages/framework/src/first.test.ts: -------------------------------------------------------------------------------- 1 | import { first } from "./first"; 2 | 3 | test("return the first item from set (first inserted) or null", function () { 4 | const set: Set = new Set(); 5 | 6 | expect(first(set)).toBe(null); 7 | 8 | set.add(1); 9 | set.add(2); 10 | set.add(3); 11 | 12 | expect(first(set)).toBe(1); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/framework/src/first.ts: -------------------------------------------------------------------------------- 1 | export function first(items: Set): null | T { 2 | for (let _item of items) { 3 | return _item; 4 | } 5 | 6 | return null; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/getRouterCallback.test.ts: -------------------------------------------------------------------------------- 1 | import { getRouterCallback } from "./getRouterCallback"; 2 | 3 | test("ensures that route is set", function () { 4 | const routes = { 5 | test(foo: string) {}, 6 | }; 7 | 8 | expect(getRouterCallback(routes, "test")).toBe(routes.test); 9 | }); 10 | 11 | test("throws user friendly error message if route is not set", function () { 12 | const routes = { 13 | test(foo: string) {}, 14 | }; 15 | 16 | expect(function () { 17 | // @ts-ignore - we are testing the runtime behavior 18 | getRouterCallback(routes, "bar"); 19 | }).toThrow(`Available keys are: "test"`); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/framework/src/getRouterCallback.ts: -------------------------------------------------------------------------------- 1 | import type { MessageEventRouter } from "./MessageEventRouter.type"; 2 | import type { MessageEventMultiRouter } from "./MessageEventMultiRouter.type"; 3 | 4 | export function getRouterCallback< 5 | Router extends MessageEventRouter | MessageEventMultiRouter, 6 | Type extends keyof Router 7 | >(router: Router, type: Type): Router[Type] { 8 | if ("function" !== typeof router[type]) { 9 | throw new Error( 10 | `MessageEvent type has no assigned callback: "${type}". Available keys are: "${Object.keys(router).join( 11 | '", "' 12 | )}".` 13 | ); 14 | } 15 | 16 | return router[type]; 17 | } 18 | -------------------------------------------------------------------------------- /packages/framework/src/handleRPCResponse.test.ts: -------------------------------------------------------------------------------- 1 | import { generateUUID } from "../../math/src/generateUUID"; 2 | 3 | import { createRPCLookupTable } from "./createRPCLookupTable"; 4 | import { handleRPCResponse } from "./handleRPCResponse"; 5 | 6 | test("calls function stored in the RPCLookupTable based on response parameter", function (done) { 7 | const uuid = generateUUID(); 8 | const rpcLookupTable = createRPCLookupTable(); 9 | 10 | rpcLookupTable[uuid] = function (data: any) { 11 | expect(data.foo).toBe("bar"); 12 | expect(data.rpc).toBe(uuid); 13 | 14 | done(); 15 | }; 16 | 17 | const handler = handleRPCResponse(rpcLookupTable); 18 | 19 | handler({ 20 | foo: "bar", 21 | rpc: uuid, 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/framework/src/invoke.test.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "./invoke"; 2 | import { noop } from "./noop"; 3 | 4 | test("invokes the function with no arguments", function () { 5 | const set: Set<() => void> = new Set(); 6 | 7 | const mockCallback1 = jest.fn(noop); 8 | const mockCallback2 = jest.fn(noop); 9 | 10 | set.add(mockCallback1); 11 | set.add(mockCallback2); 12 | 13 | set.forEach(invoke); 14 | 15 | expect(mockCallback1.mock.calls).toHaveLength(1); 16 | expect(mockCallback2.mock.calls).toHaveLength(1); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/framework/src/invoke.ts: -------------------------------------------------------------------------------- 1 | export function invoke(fn: () => void): void { 2 | fn(); 3 | } 4 | -------------------------------------------------------------------------------- /packages/framework/src/isCanvasTransferControlToOffscreenSupported.ts: -------------------------------------------------------------------------------- 1 | let _isSupported: boolean = false; 2 | let _isTested: boolean = false; 3 | 4 | function _test(): boolean { 5 | if (!globalThis.HTMLCanvasElement) { 6 | return false; 7 | } 8 | 9 | // @ts-ignore OffscreenCanvas is experimental 10 | return "function" === typeof HTMLCanvasElement.prototype.transferControlToOffscreen; 11 | } 12 | 13 | export function isCanvasTransferControlToOffscreenSupported(): boolean { 14 | if (_isTested) { 15 | return _isSupported; 16 | } 17 | 18 | _isSupported = _test(); 19 | _isTested = true; 20 | 21 | return _isSupported; 22 | } 23 | -------------------------------------------------------------------------------- /packages/framework/src/isCustomEvent.ts: -------------------------------------------------------------------------------- 1 | export function isCustomEvent(evt: Event): evt is CustomEvent { 2 | if (evt.isTrusted) { 3 | // Definitely not a CustomEvent. 4 | return false; 5 | } 6 | 7 | return evt instanceof CustomEvent; 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/isInDimensionsBounds.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsIndices } from "./DimensionsIndices.enum"; 2 | 3 | export function isInDimensionsBounds(dimensionsState: Uint32Array, clientX: number, clientY: number): boolean { 4 | // prettier-ignore 5 | return clientX > dimensionsState[DimensionsIndices.P_LEFT] 6 | && clientY > dimensionsState[DimensionsIndices.P_TOP] 7 | && clientX < dimensionsState[DimensionsIndices.P_RIGHT] 8 | && clientY < dimensionsState[DimensionsIndices.P_BOTTOM] 9 | ; 10 | } 11 | -------------------------------------------------------------------------------- /packages/framework/src/isMesh.ts: -------------------------------------------------------------------------------- 1 | import type { Mesh as IMesh } from "three/src/objects/Mesh"; 2 | 3 | export function isMesh(item: any): item is IMesh { 4 | if ("object" !== typeof item || !item) { 5 | return false; 6 | } 7 | 8 | return item.isMesh && item.isObject3D; 9 | } 10 | -------------------------------------------------------------------------------- /packages/framework/src/isMountable.ts: -------------------------------------------------------------------------------- 1 | import type { Mountable } from "./Mountable.interface"; 2 | 3 | export function isMountable(item: any): item is Mountable { 4 | return true === (item as Mountable).isMountable; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/isNameable.ts: -------------------------------------------------------------------------------- 1 | import type { Nameable } from "./Nameable.interface"; 2 | 3 | export function isNameable(item: any): item is Nameable { 4 | if ("object" !== typeof item) { 5 | return false; 6 | } 7 | 8 | if (!("id" in item) || "string" !== typeof item.id) { 9 | return false; 10 | } 11 | 12 | if (!("name" in item) || "string" !== typeof item.name) { 13 | return false; 14 | } 15 | 16 | return true; 17 | } 18 | -------------------------------------------------------------------------------- /packages/framework/src/isPollablePreloading.ts: -------------------------------------------------------------------------------- 1 | import type { PollablePreloading } from "./PollablePreloading.interface"; 2 | 3 | export function isPollablePreloading(object: object): object is PollablePreloading { 4 | return true === (object as PollablePreloading).isPollablePreloading; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/isPromise.ts: -------------------------------------------------------------------------------- 1 | export function isPromise(item: any): item is Promise { 2 | if ("object" !== typeof item) { 3 | return false; 4 | } 5 | 6 | return item instanceof Promise; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/isRequestAnimationFrameSupported.ts: -------------------------------------------------------------------------------- 1 | export function isRequestAnimationFrameSupported(): boolean { 2 | return "function" === typeof globalThis.requestAnimationFrame; 3 | } 4 | -------------------------------------------------------------------------------- /packages/framework/src/isSharedArrayBuffer.ts: -------------------------------------------------------------------------------- 1 | import { isSharedArrayBufferSupported } from "./isSharedArrayBufferSupported"; 2 | 3 | export function isSharedArrayBuffer(item: any): item is SharedArrayBuffer { 4 | return isSharedArrayBufferSupported() && item instanceof globalThis.SharedArrayBuffer; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/isSharedArrayBufferSupported.ts: -------------------------------------------------------------------------------- 1 | export function isSharedArrayBufferSupported(): boolean { 2 | return "function" === typeof globalThis.SharedArrayBuffer; 3 | } 4 | -------------------------------------------------------------------------------- /packages/framework/src/isStatsReport.ts: -------------------------------------------------------------------------------- 1 | import type { StatsReport } from "./StatsReport.type"; 2 | 3 | export function isStatsReport(report: any): report is StatsReport { 4 | if ("object" !== typeof report) { 5 | return false; 6 | } 7 | 8 | return "string" === typeof report.debugName; 9 | } 10 | -------------------------------------------------------------------------------- /packages/framework/src/longestVector3.test.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import { longestVector3 } from "./longestVector3"; 4 | 5 | test("picks a longest vector from the list of vectors", function () { 6 | const v1 = new Vector3(0, 0, 0); 7 | const v2 = new Vector3(10, 0, 0); 8 | const v3 = new Vector3(8, 8, 0); 9 | const v4 = new Vector3(0, -10, 0); 10 | 11 | expect(longestVector3(v1, v2, v3, v4)).toBe(v3); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/framework/src/longestVector3.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3 } from "three/src/math/Vector3"; 2 | 3 | function _reduceToLongest(longest: Vector3, current: Vector3): Vector3 { 4 | if (longest.lengthSq() > current.lengthSq()) { 5 | return longest; 6 | } 7 | 8 | return current; 9 | } 10 | 11 | export function longestVector3(...vectors: Array): Vector3 { 12 | if (vectors.length < 1) { 13 | throw new Error("Expected vectors to compare."); 14 | } 15 | 16 | return vectors.reduce(_reduceToLongest, vectors[0]); 17 | } 18 | -------------------------------------------------------------------------------- /packages/framework/src/mount.ts: -------------------------------------------------------------------------------- 1 | import { name } from "./name"; 2 | 3 | import type { Logger } from "loglevel"; 4 | 5 | import type { Mountable } from "./Mountable.interface"; 6 | 7 | export function mount(logger: Logger, mount: Mountable): void { 8 | if (mount.state.isMounted) { 9 | throw new Error(`Mount is already mounted: "${name(mount)}"`); 10 | } 11 | 12 | logger.debug(`MOUNT(${name(mount)})`); 13 | mount.mount(); 14 | 15 | if (!mount.state.isMounted) { 16 | throw new Error( 17 | `Mount needs to go into 'mounted' state immediately after calling '.mount' method: "${name(mount)}"` 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/framework/src/mountAll.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "./invoke"; 2 | 3 | import type { MountableCallback } from "./MountableCallback.type"; 4 | 5 | export function mountAll(mountables: Set): void { 6 | mountables.forEach(invoke); 7 | mountables.clear(); 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/must.test.ts: -------------------------------------------------------------------------------- 1 | import { must } from "./must"; 2 | 3 | test("passes through element if it is set", function () { 4 | expect(must(5)).toBe(5); 5 | }); 6 | 7 | test("fails when element is not set", function () { 8 | expect(function () { 9 | must(null); 10 | }).toThrow(); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/framework/src/must.ts: -------------------------------------------------------------------------------- 1 | export function must(item: undefined | null | T, errorMessage: string = "Expected item to be not-null."): T { 2 | if (null === item || "undefined" === typeof item) { 3 | throw new Error(errorMessage); 4 | } 5 | 6 | return item; 7 | } 8 | -------------------------------------------------------------------------------- /packages/framework/src/name.ts: -------------------------------------------------------------------------------- 1 | import type { Nameable } from "./Nameable.interface"; 2 | 3 | export function name(nameable: Nameable): string { 4 | return `${nameable.name}#${nameable.id}`; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/noop.ts: -------------------------------------------------------------------------------- 1 | export function noop(): void {} 2 | -------------------------------------------------------------------------------- /packages/framework/src/onlyOne.ts: -------------------------------------------------------------------------------- 1 | import { first } from "./first"; 2 | import { must } from "./must"; 3 | 4 | export function onlyOne(items: Set, message: string = "Expected exactly one item in set."): T { 5 | if (items.size !== 1) { 6 | throw new Error(message); 7 | } 8 | 9 | return must(first(items), message); 10 | } 11 | -------------------------------------------------------------------------------- /packages/framework/src/passiveEventListener.ts: -------------------------------------------------------------------------------- 1 | export const passiveEventListener = Object.freeze({ 2 | passive: true, 3 | }); 4 | -------------------------------------------------------------------------------- /packages/framework/src/pause.ts: -------------------------------------------------------------------------------- 1 | import { name } from "./name"; 2 | 3 | import type { Logger } from "loglevel"; 4 | 5 | import type { Pauseable } from "./Pauseable.interface"; 6 | 7 | export function pause(logger: Logger, pauseable: Pauseable): void { 8 | if (pauseable.state.isPaused) { 9 | throw new Error(`Scene is already paused: "${name(pauseable)}"`); 10 | } 11 | 12 | // logger.trace(`PAUSE(${name(pauseable)})`); 13 | pauseable.pause(); 14 | 15 | if (!pauseable.state.isPaused) { 16 | throw new Error( 17 | `Scene needs to go into 'paused' state immediately after calling '.pause' method: "${name(pauseable)}"` 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/framework/src/routeMessages.ts: -------------------------------------------------------------------------------- 1 | import { getRouterCallback } from "./getRouterCallback"; 2 | 3 | import type { MessageEventData } from "./MessageEventData.type"; 4 | import type { MessageEventRouter } from "./MessageEventRouter.type"; 5 | 6 | export function routeMessages( 7 | evt: ExtendableMessageEvent | MessageEvent, 8 | data: MessageEventData, 9 | routes: MessageEventRouter 10 | ): void { 11 | for (let type in data) { 12 | if (data.hasOwnProperty(type)) { 13 | getRouterCallback(routes, type)(data[type], evt); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/framework/src/routeMultiMessage.ts: -------------------------------------------------------------------------------- 1 | import { getRouterCallback } from "./getRouterCallback"; 2 | 3 | import type { MessageEventData } from "./MessageEventData.type"; 4 | import type { MessageEventMultiRouter } from "./MessageEventMultiRouter.type"; 5 | 6 | export function routeMultiMessage( 7 | messagePort: MessagePort, 8 | evt: ExtendableMessageEvent | MessageEvent, 9 | data: MessageEventData, 10 | router: MessageEventMultiRouter 11 | ): void { 12 | for (let type in data) { 13 | if (data.hasOwnProperty(type)) { 14 | getRouterCallback(router, type)(messagePort, data[type], evt); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/framework/src/unary.test.ts: -------------------------------------------------------------------------------- 1 | import { unary } from "./unary"; 2 | 3 | test("taskes only one argument and ignores the rest", function () { 4 | const sum = function (a: number, b: number = 0) { 5 | return a + b; 6 | }; 7 | 8 | // @ts-ignore: Expected 1 arguments, but got 2 9 | expect(unary(sum)(1, 5)).toBe(1); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/framework/src/unary.ts: -------------------------------------------------------------------------------- 1 | export function unary(fn: (arg: T) => U): (arg: T) => U { 2 | return function (arg: T): U { 3 | return fn(arg); 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /packages/framework/src/unmount.ts: -------------------------------------------------------------------------------- 1 | import { name } from "./name"; 2 | 3 | import type { Logger } from "loglevel"; 4 | 5 | import type { Mountable } from "./Mountable.interface"; 6 | 7 | export function unmount(logger: Logger, mount: Mountable): void { 8 | if (!mount.state.isMounted) { 9 | throw new Error(`Mount is already unmounted: "${name(mount)}"`); 10 | } 11 | 12 | logger.debug(`UNMOUNT(${name(mount)})`); 13 | mount.unmount(); 14 | 15 | if (mount.state.isMounted) { 16 | throw new Error(`Mount must go into 'unmounted' state immediately after calling '.unmount': "${name(mount)}"`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/framework/src/unmountAll.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "./invoke"; 2 | 3 | import type { UnmountableCallback } from "./UnmountableCallback.type"; 4 | 5 | export function unmountAll(unmountables: Set): void { 6 | unmountables.forEach(invoke); 7 | unmountables.clear(); 8 | } 9 | -------------------------------------------------------------------------------- /packages/framework/src/unpause.ts: -------------------------------------------------------------------------------- 1 | import { name } from "./name"; 2 | 3 | import type { Logger } from "loglevel"; 4 | 5 | import type { Pauseable } from "./Pauseable.interface"; 6 | 7 | export function unpause(logger: Logger, pauseable: Pauseable): void { 8 | if (!pauseable.state.isPaused) { 9 | throw new Error(`Scene is already unpaused: "${name(pauseable)}"`); 10 | } 11 | 12 | // logger.trace(`UNPAUSE(${name(pauseable)})`); 13 | pauseable.unpause(); 14 | 15 | if (pauseable.state.isPaused) { 16 | throw new Error( 17 | `Scene needs to go into 'unpaused' state immediately after calling '.unpause' method: "${name(pauseable)}"` 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/framework/src/updateOrthographicCameraAspect.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsIndices } from "./DimensionsIndices.enum"; 2 | 3 | import type { OrthographicCamera } from "three/src/cameras/OrthographicCamera"; 4 | 5 | export function updateOrthographicCameraAspect( 6 | dimensionsState: Uint32Array, 7 | camera: OrthographicCamera, 8 | frustumSize: number 9 | ): void { 10 | const aspect = dimensionsState[DimensionsIndices.D_WIDTH] / dimensionsState[DimensionsIndices.D_HEIGHT]; 11 | 12 | camera.left = (-frustumSize * aspect) / 2; 13 | camera.right = (frustumSize * aspect) / 2; 14 | camera.top = frustumSize / 2; 15 | camera.bottom = -frustumSize / 2; 16 | 17 | camera.updateProjectionMatrix(); 18 | } 19 | -------------------------------------------------------------------------------- /packages/framework/src/updatePerspectiveCameraAspect.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsIndices } from "./DimensionsIndices.enum"; 2 | 3 | import type { PerspectiveCamera } from "three/src/cameras/PerspectiveCamera"; 4 | 5 | export function updatePerspectiveCameraAspect(dimensionsState: Uint32Array, camera: PerspectiveCamera): void { 6 | const aspect = dimensionsState[DimensionsIndices.D_WIDTH] / dimensionsState[DimensionsIndices.D_HEIGHT]; 7 | 8 | if (camera.aspect === aspect) { 9 | return; 10 | } 11 | 12 | camera.aspect = aspect; 13 | camera.updateProjectionMatrix(); 14 | } 15 | -------------------------------------------------------------------------------- /packages/i18n/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/i18n/README.md: -------------------------------------------------------------------------------- 1 | # `i18n` 2 | 3 | `i18next` facades that make it compatible with other services and the 4 | `framework` package. 5 | -------------------------------------------------------------------------------- /packages/i18n/src/InternationalizationService.interface.ts: -------------------------------------------------------------------------------- 1 | import type { i18n } from "i18next"; 2 | 3 | import type { Preloadable } from "../../framework/src/Preloadable.interface"; 4 | import type { RegistersMessagePort } from "../../framework/src/RegistersMessagePort.interface"; 5 | import type { Service } from "../../framework/src/Service.interface"; 6 | 7 | export interface InternationalizationService extends Preloadable, RegistersMessagePort, Service { 8 | i18next: i18n; 9 | } 10 | -------------------------------------------------------------------------------- /packages/i18n/src/LoglevelPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { Logger } from "loglevel"; 2 | import type { LoggerModule } from "i18next"; 3 | 4 | function _concatArgs(args: Array): string { 5 | return args.filter(_isString).join('", "'); 6 | } 7 | 8 | function _isString(arg: any): boolean { 9 | return "string" === typeof arg; 10 | } 11 | 12 | export function LoglevelPlugin(logger: Logger): LoggerModule { 13 | return Object.seal({ 14 | type: "logger", 15 | 16 | log(args: Array) { 17 | logger.debug(`I18N("${_concatArgs(args)}")`); 18 | }, 19 | warn(args: Array) { 20 | logger.warn(`I18N("${_concatArgs(args)}")`); 21 | }, 22 | error(args: Array) { 23 | logger.error(`I18N("${_concatArgs(args)}")`); 24 | }, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /packages/i18n/src/getI18NextKeyNamespace.test.ts: -------------------------------------------------------------------------------- 1 | import { getI18NextKeyNamespace } from "./getI18NextKeyNamespace"; 2 | 3 | test("finds a translation key", function () { 4 | expect(getI18NextKeyNamespace("foo:bar")).toBe("foo"); 5 | expect(getI18NextKeyNamespace("baz")).toBe(""); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/i18n/src/getI18NextKeyNamespace.ts: -------------------------------------------------------------------------------- 1 | const NS_SEPARATOR: string = ":"; 2 | 3 | export function getI18NextKeyNamespace(key: string): string { 4 | if (-1 === key.indexOf(NS_SEPARATOR)) { 5 | return ""; 6 | } 7 | 8 | return key.split(NS_SEPARATOR)[0]; 9 | } 10 | -------------------------------------------------------------------------------- /packages/input/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/input/README.md: -------------------------------------------------------------------------------- 1 | # `input` 2 | 3 | Package that handles user input devices. 4 | -------------------------------------------------------------------------------- /packages/input/src/KeyboardKeyName.type.ts: -------------------------------------------------------------------------------- 1 | import { KeyboardIndices } from "./KeyboardIndices.enum"; 2 | 3 | export type KeyboardKeyName = keyof typeof KeyboardIndices; 4 | -------------------------------------------------------------------------------- /packages/input/src/KeyboardObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | import type { KeyboardObserverState } from "./KeyboardObserverState.type"; 5 | 6 | export interface KeyboardObserver extends MainLoopUpdatable, Service { 7 | readonly isKeyboardObserver: true; 8 | readonly state: KeyboardObserverState; 9 | } 10 | -------------------------------------------------------------------------------- /packages/input/src/KeyboardObserverState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | 3 | export type KeyboardObserverState = MainLoopUpdatableState & { 4 | lastUpdate: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/input/src/KeyboardState.ts: -------------------------------------------------------------------------------- 1 | import { KeyboardIndices } from "./KeyboardIndices.enum"; 2 | 3 | function createEmptyState(usesSharedBuffer: boolean): Uint8Array { 4 | if (usesSharedBuffer) { 5 | return new Uint8Array(new SharedArrayBuffer(KeyboardIndices.__TOTAL * Uint8Array.BYTES_PER_ELEMENT)); 6 | } 7 | 8 | return new Uint8Array(KeyboardIndices.__TOTAL); 9 | } 10 | 11 | export const KeyboardState = Object.freeze({ 12 | createEmptyState: createEmptyState, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/input/src/MouseButtons.enum.ts: -------------------------------------------------------------------------------- 1 | export const enum MouseButtons { 2 | Primary = 1, 3 | Secondary = 2, 4 | Middle = 4, 5 | Generic1 = 8, 6 | Generic2 = 16, 7 | } 8 | -------------------------------------------------------------------------------- /packages/input/src/MouseIndices.enum.ts: -------------------------------------------------------------------------------- 1 | export const enum MouseIndices { 2 | // Mouse 3 | M_BUTTON_4, 4 | M_BUTTON_5, 5 | M_BUTTON_L, 6 | M_BUTTON_M, 7 | M_BUTTON_R, 8 | M_CLIENT_X, 9 | M_CLIENT_Y, 10 | M_DOWN_INITIAL_CLIENT_X, 11 | M_DOWN_INITIAL_CLIENT_Y, 12 | M_DOWN_LAST_UPDATE, 13 | M_DOWN_TRAVEL_DISTANCE, 14 | M_IN_BOUNDS, 15 | M_INITIATED_BY_ROOT_ELEMENT, 16 | M_LAST_USED, 17 | M_RELATIVE_X, 18 | M_RELATIVE_Y, 19 | M_UP_CLIENT_X, 20 | M_UP_CLIENT_Y, 21 | M_UP_LAST_UPDATE, 22 | M_VECTOR_SCALE, 23 | 24 | // This one NEEDS to be last to get the enum size correctly 25 | __TOTAL, 26 | } 27 | -------------------------------------------------------------------------------- /packages/input/src/MouseObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | import type { MouseObserverState } from "./MouseObserverState.type"; 5 | 6 | export interface MouseObserver extends MainLoopUpdatable, Service { 7 | readonly state: MouseObserverState; 8 | readonly isMouseObserver: true; 9 | } 10 | -------------------------------------------------------------------------------- /packages/input/src/MouseObserverState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | 3 | export type MouseObserverState = MainLoopUpdatableState & { 4 | lastUpdate: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/input/src/MouseWheelObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Service } from "../../framework/src/Service.interface"; 2 | 3 | export interface MouseWheelObserver extends Service { 4 | isMouseWheelObserver: true; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/PointerTouch.type.ts: -------------------------------------------------------------------------------- 1 | export type PointerTouch = { 2 | CLIENT_X: number; 3 | CLIENT_Y: number; 4 | DOWN_INITIAL_CLIENT_X: number; 5 | DOWN_INITIAL_CLIENT_Y: number; 6 | DOWN_INITIAL_LAST_UPDATE: number; 7 | IN_BOUNDS: number; 8 | PRESSURE: number; 9 | RELATIVE_X: number; 10 | RELATIVE_Y: number; 11 | UP_CLIENT_X: number; 12 | UP_CLIENT_Y: number; 13 | UP_LAST_UPDATE: number; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/input/src/Raycastable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Object3D } from "three/src/core/Object3D"; 2 | 3 | import type { RaycastableState } from "./RaycastableState.type"; 4 | 5 | export interface Raycastable { 6 | readonly isRaycastable: true; 7 | readonly raycasterObject3D: Object3D; 8 | readonly state: RaycastableState; 9 | } 10 | -------------------------------------------------------------------------------- /packages/input/src/RaycastableState.type.ts: -------------------------------------------------------------------------------- 1 | export type RaycastableState = { 2 | isRayIntersecting: boolean; 3 | // Can be set to false to be permanently removed from the raycast pool. 4 | needsRaycast: boolean; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/input/src/Raycaster.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | 3 | import type { Raycastable } from "./Raycastable.interface"; 4 | import type { RaycasterState } from "./RaycasterState.type"; 5 | 6 | export interface Raycaster extends MainLoopUpdatable { 7 | readonly isRaycaster: true; 8 | readonly raycastables: Set; 9 | readonly state: RaycasterState; 10 | 11 | reset(): void; 12 | } 13 | -------------------------------------------------------------------------------- /packages/input/src/RaycasterState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | 3 | export type RaycasterState = MainLoopUpdatableState & { 4 | hasIntersections: boolean; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/input/src/TouchObserver.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | import type { TouchObserverState } from "./TouchObserverState.type"; 5 | 6 | export interface TouchObserver extends MainLoopUpdatable, Service { 7 | readonly state: TouchObserverState; 8 | readonly isTouchObserver: true; 9 | } 10 | -------------------------------------------------------------------------------- /packages/input/src/TouchObserverState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | 3 | export type TouchObserverState = MainLoopUpdatableState & { 4 | lastUpdate: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/input/src/UserInputController.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 4 | import type { Mountable } from "../../framework/src/Mountable.interface"; 5 | import type { Pauseable } from "../../framework/src/Pauseable.interface"; 6 | 7 | import type { UserInputControllerState } from "./UserInputControllerState.type"; 8 | 9 | export interface UserInputController extends MainLoopUpdatable, Mountable, Pauseable { 10 | readonly cameraTransitionRequest: Vector3; 11 | readonly isUserInputController: true; 12 | readonly state: UserInputControllerState; 13 | } 14 | -------------------------------------------------------------------------------- /packages/input/src/UserInputControllerState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | import type { MountableState } from "../../framework/src/MountableState.type"; 3 | import type { PauseableState } from "../../framework/src/PauseableState.type"; 4 | 5 | export type UserInputControllerState = MainLoopUpdatableState & MountableState & PauseableState; 6 | -------------------------------------------------------------------------------- /packages/input/src/UserInputMouseController.interface.ts: -------------------------------------------------------------------------------- 1 | import type { UserInputController } from "./UserInputController.interface"; 2 | import type { UserInputMouseControllerState } from "./UserInputMouseControllerState.type"; 3 | 4 | export interface UserInputMouseController extends UserInputController { 5 | readonly state: UserInputMouseControllerState; 6 | } 7 | -------------------------------------------------------------------------------- /packages/input/src/UserInputMouseControllerState.type.ts: -------------------------------------------------------------------------------- 1 | import { UserInputControllerState } from "./UserInputControllerState.type"; 2 | 3 | export type UserInputMouseControllerState = UserInputControllerState & { 4 | isPressStarted: boolean; 5 | isPressStartedWithIntersection: boolean; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/input/src/computePointerStretchVector.test.ts: -------------------------------------------------------------------------------- 1 | import { Vector2 } from "three/src/math/Vector2"; 2 | 3 | import { computePointerStretchVector } from "./computePointerStretchVector"; 4 | 5 | test("computes stretch vector", function () { 6 | const target = new Vector2(0, 0); 7 | 8 | computePointerStretchVector(target, 0, 100, 0, 0, 100); 9 | 10 | expect(target.x).toBe(1); 11 | expect(target.y).toBe(-0); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/input/src/computePointerStretchVector.ts: -------------------------------------------------------------------------------- 1 | import type { Vector2 } from "three/src/math/Vector2"; 2 | 3 | export function computePointerStretchVector( 4 | target: Vector2, 5 | initialClientX: number, 6 | currentClientX: number, 7 | initialClientY: number, 8 | currentClientY: number, 9 | spread: number = 200 10 | ): void { 11 | const xDiff: number = currentClientX - initialClientX; 12 | const yDiff: number = currentClientY - initialClientY; 13 | 14 | target.x = (xDiff / spread) * 2; 15 | target.y = ((-1 * yDiff) / spread) * 2; 16 | target.clampLength(0, 1); 17 | } 18 | -------------------------------------------------------------------------------- /packages/input/src/computePrimaryTouchStretchVector.ts: -------------------------------------------------------------------------------- 1 | import { computePointerStretchVector } from "./computePointerStretchVector"; 2 | import { TouchIndices } from "./TouchIndices.enum"; 3 | 4 | import type { Vector2 } from "three/src/math/Vector2"; 5 | 6 | export function computePrimaryTouchStretchVector(target: Vector2, touchState: Int32Array, spread: number = 200): void { 7 | computePointerStretchVector( 8 | target, 9 | touchState[TouchIndices.T0_DOWN_INITIAL_CLIENT_X], 10 | touchState[TouchIndices.T0_CLIENT_X], 11 | touchState[TouchIndices.T0_DOWN_INITIAL_CLIENT_Y], 12 | touchState[TouchIndices.T0_CLIENT_Y], 13 | spread 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/input/src/getMousePointerVectorX.ts: -------------------------------------------------------------------------------- 1 | import { getPointerVectorX } from "./getPointerVectorX"; 2 | import { MouseIndices } from "./MouseIndices.enum"; 3 | 4 | export function getMousePointerVectorX(dimensionsState: Uint32Array, mouseState: Int32Array): number { 5 | return getPointerVectorX(dimensionsState, mouseState[MouseIndices.M_RELATIVE_X]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/input/src/getMousePointerVectorY.ts: -------------------------------------------------------------------------------- 1 | import { getPointerVectorY } from "./getPointerVectorY"; 2 | import { MouseIndices } from "./MouseIndices.enum"; 3 | 4 | export function getMousePointerVectorY(dimensionsState: Uint32Array, mouseState: Int32Array): number { 5 | return getPointerVectorY(dimensionsState, mouseState[MouseIndices.M_RELATIVE_Y]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/input/src/getPointerVectorX.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsIndices } from "../../framework/src/DimensionsIndices.enum"; 2 | 3 | export function getPointerVectorX(dimensionsState: Uint32Array, relativeX: number): number { 4 | return (relativeX / dimensionsState[DimensionsIndices.D_WIDTH]) * 2 - 1; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/getPointerVectorY.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsIndices } from "../../framework/src/DimensionsIndices.enum"; 2 | 3 | export function getPointerVectorY(dimensionsState: Uint32Array, relativeY: number): number { 4 | return -1 * (relativeY / dimensionsState[DimensionsIndices.D_HEIGHT]) * 2 + 1; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/getPrimaryPointerVectorX.ts: -------------------------------------------------------------------------------- 1 | import { getMousePointerVectorX } from "./getMousePointerVectorX"; 2 | import { getPrimaryTouchVectorX } from "./getPrimaryTouchVectorX"; 3 | import { MouseIndices } from "./MouseIndices.enum"; 4 | import { TouchIndices } from "./TouchIndices.enum"; 5 | 6 | export function getPrimaryPointerVectorX( 7 | dimensionsState: Uint32Array, 8 | mouseState: Int32Array, 9 | touchState: Int32Array 10 | ): number { 11 | if (mouseState[MouseIndices.M_LAST_USED] > touchState[TouchIndices.T_LAST_USED]) { 12 | return getMousePointerVectorX(dimensionsState, mouseState); 13 | } 14 | 15 | return getPrimaryTouchVectorX(dimensionsState, touchState); 16 | } 17 | -------------------------------------------------------------------------------- /packages/input/src/getPrimaryPointerVectorY.ts: -------------------------------------------------------------------------------- 1 | import { getMousePointerVectorY } from "./getMousePointerVectorY"; 2 | import { getPrimaryTouchVectorY } from "./getPrimaryTouchVectorY"; 3 | import { MouseIndices } from "./MouseIndices.enum"; 4 | import { TouchIndices } from "./TouchIndices.enum"; 5 | 6 | export function getPrimaryPointerVectorY( 7 | dimensionsState: Uint32Array, 8 | mouseState: Int32Array, 9 | touchState: Int32Array 10 | ): number { 11 | if (mouseState[MouseIndices.M_LAST_USED] > touchState[TouchIndices.T_LAST_USED]) { 12 | return getMousePointerVectorY(dimensionsState, mouseState); 13 | } 14 | 15 | return getPrimaryTouchVectorY(dimensionsState, touchState); 16 | } 17 | -------------------------------------------------------------------------------- /packages/input/src/getPrimaryTouchInitialClientX.ts: -------------------------------------------------------------------------------- 1 | import { TouchIndices } from "./TouchIndices.enum"; 2 | 3 | export function getPrimaryTouchInitialClientX(touchState: Int32Array): number { 4 | return touchState[TouchIndices.T0_DOWN_INITIAL_CLIENT_X]; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/getPrimaryTouchInitialClientY.ts: -------------------------------------------------------------------------------- 1 | import { TouchIndices } from "./TouchIndices.enum"; 2 | 3 | export function getPrimaryTouchInitialClientY(touchState: Int32Array): number { 4 | return touchState[TouchIndices.T0_DOWN_INITIAL_CLIENT_Y]; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/getPrimaryTouchVectorX.ts: -------------------------------------------------------------------------------- 1 | import { getPointerVectorX } from "./getPointerVectorX"; 2 | import { TouchIndices } from "./TouchIndices.enum"; 3 | 4 | export function getPrimaryTouchVectorX(dimensionsState: Uint32Array, touchState: Int32Array): number { 5 | return getPointerVectorX(dimensionsState, touchState[TouchIndices.T0_RELATIVE_X]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/input/src/getPrimaryTouchVectorY.ts: -------------------------------------------------------------------------------- 1 | import { getPointerVectorY } from "./getPointerVectorY"; 2 | import { TouchIndices } from "./TouchIndices.enum"; 3 | 4 | export function getPrimaryTouchVectorY(dimensionsState: Uint32Array, touchState: Int32Array): number { 5 | return getPointerVectorY(dimensionsState, touchState[TouchIndices.T0_RELATIVE_Y]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/input/src/isKeyboardKeyName.ts: -------------------------------------------------------------------------------- 1 | import { KeyboardIndices } from "./KeyboardIndices.enum"; 2 | 3 | import type { KeyboardKeyName } from "./KeyboardKeyName.type"; 4 | 5 | export function isKeyboardKeyName(key: string): key is KeyboardKeyName { 6 | return KeyboardIndices.hasOwnProperty(key); 7 | } 8 | -------------------------------------------------------------------------------- /packages/input/src/isMousePointerInDimensionsBounds.ts: -------------------------------------------------------------------------------- 1 | import { MouseIndices } from "./MouseIndices.enum"; 2 | 3 | export function isMousePointerInDimensionsBounds(dimensionsState: Uint32Array, mouseState: Int32Array): boolean { 4 | return mouseState[MouseIndices.M_IN_BOUNDS] > 0; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/isPotentiallyMouseClick.ts: -------------------------------------------------------------------------------- 1 | import { isPrimaryMouseButtonPressed } from "./isPrimaryMouseButtonPressed"; 2 | import { MouseIndices } from "./MouseIndices.enum"; 3 | 4 | export function isPotentiallyMouseClick(mouseState: Int32Array): boolean { 5 | if (!isPrimaryMouseButtonPressed(mouseState)) { 6 | return false; 7 | } 8 | 9 | return mouseState[MouseIndices.M_DOWN_TRAVEL_DISTANCE] < 5; 10 | } 11 | -------------------------------------------------------------------------------- /packages/input/src/isPrimaryMouseButtonPressInitiatedByRootElement.ts: -------------------------------------------------------------------------------- 1 | import { MouseIndices } from "./MouseIndices.enum"; 2 | 3 | export function isPrimaryMouseButtonPressInitiatedByRootElement(mouseState: Int32Array): boolean { 4 | return Boolean(mouseState[MouseIndices.M_INITIATED_BY_ROOT_ELEMENT]); 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/isPrimaryMouseButtonPressed.ts: -------------------------------------------------------------------------------- 1 | import { MouseIndices } from "./MouseIndices.enum"; 2 | 3 | export function isPrimaryMouseButtonPressed(mouseState: Int32Array): boolean { 4 | return mouseState[MouseIndices.M_BUTTON_L] > 0 && mouseState[MouseIndices.M_IN_BOUNDS] > 0; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/isPrimaryPointerPressed.ts: -------------------------------------------------------------------------------- 1 | import { isPrimaryMouseButtonPressed } from "./isPrimaryMouseButtonPressed"; 2 | import { isPrimaryTouchPressed } from "./isPrimaryTouchPressed"; 3 | 4 | export function isPrimaryPointerPressed(mouseState: Int32Array, touchState: Int32Array): boolean { 5 | return isPrimaryMouseButtonPressed(mouseState) || isPrimaryTouchPressed(touchState); 6 | } 7 | -------------------------------------------------------------------------------- /packages/input/src/isPrimaryTouchInDimensionsBounds.ts: -------------------------------------------------------------------------------- 1 | import { TouchIndices } from "./TouchIndices.enum"; 2 | 3 | export function isPrimaryTouchInDimensionsBounds(dimensionsState: Uint32Array, touchState: Int32Array): boolean { 4 | return touchState[TouchIndices.T_TOTAL] > 0 && touchState[TouchIndices.T0_IN_BOUNDS] > 0; 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/isPrimaryTouchInitiatedByRootElement.ts: -------------------------------------------------------------------------------- 1 | import { TouchIndices } from "./TouchIndices.enum"; 2 | 3 | export function isPrimaryTouchInitiatedByRootElement(touchState: Int32Array): boolean { 4 | return Boolean(touchState[TouchIndices.T_INITIATED_BY_ROOT_ELEMENT]); 5 | } 6 | -------------------------------------------------------------------------------- /packages/input/src/isPrimaryTouchPressed.ts: -------------------------------------------------------------------------------- 1 | import { TouchIndices } from "./TouchIndices.enum"; 2 | 3 | export function isPrimaryTouchPressed(touchState: Int32Array): boolean { 4 | return touchState[TouchIndices.T_TOTAL] > 0 && touchState[TouchIndices.T0_IN_BOUNDS] > 0; 5 | } 6 | -------------------------------------------------------------------------------- /packages/math/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/math/README.md: -------------------------------------------------------------------------------- 1 | # `math` 2 | -------------------------------------------------------------------------------- /packages/math/src/clamp.ts: -------------------------------------------------------------------------------- 1 | export function clamp(value: number, min: number, max: number): number { 2 | return Math.max(min, Math.min(max, value)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/math/src/damp.ts: -------------------------------------------------------------------------------- 1 | import { lerp } from "./lerp"; 2 | 3 | function doDamp(x: number, y: number, lambda: number, dt: number): number { 4 | return lerp(x, y, 1 - Math.exp(-lambda * dt)); 5 | } 6 | 7 | export function damp(x: number, y: number, lambda: number, dt: number): number { 8 | if (x === y) { 9 | return x; 10 | } 11 | 12 | return doDamp(x, y, lambda, dt); 13 | } 14 | -------------------------------------------------------------------------------- /packages/math/src/generateUUID.ts: -------------------------------------------------------------------------------- 1 | import { v4 } from "uuid"; 2 | 3 | export function generateUUID(): string { 4 | return v4(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/math/src/isPowerOfTwo.ts: -------------------------------------------------------------------------------- 1 | export function isPowerOfTwo(value: number): boolean { 2 | return (value & (value - 1)) === 0 && value !== 0; 3 | } 4 | -------------------------------------------------------------------------------- /packages/math/src/lerp.ts: -------------------------------------------------------------------------------- 1 | export function lerp(x: number, y: number, t: number): number { 2 | return (1 - t) * x + t * y; 3 | } 4 | -------------------------------------------------------------------------------- /packages/math/src/radToDeg.ts: -------------------------------------------------------------------------------- 1 | const RAD2DEG = 180 / Math.PI; 2 | 3 | export function radToDeg(radians: number): number { 4 | return radians * RAD2DEG; 5 | } 6 | -------------------------------------------------------------------------------- /packages/math/src/randFloat.ts: -------------------------------------------------------------------------------- 1 | export function randFloat(low: number, high: number): number { 2 | return low + Math.random() * (high - low); 3 | } 4 | -------------------------------------------------------------------------------- /packages/math/src/randInt.ts: -------------------------------------------------------------------------------- 1 | export function randInt(low: number, high: number): number { 2 | return low + Math.floor(Math.random() * (high - low + 1)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/math/src/roundToNearestMultiple.test.ts: -------------------------------------------------------------------------------- 1 | import { roundToNearestMultiple } from "./roundToNearestMultiple"; 2 | 3 | test("rounds to the nearest multiple of number", function () { 4 | expect(roundToNearestMultiple(15, 35)).toBe(30); 5 | expect(roundToNearestMultiple(50, 195)).toBe(200); 6 | expect(roundToNearestMultiple(5, 1)).toBe(0); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/math/src/roundToNearestMultiple.ts: -------------------------------------------------------------------------------- 1 | export function roundToNearestMultiple(multiple: number, val: number): number { 2 | const rest = val % multiple; 3 | if (rest <= multiple / 2) { 4 | return val - rest; 5 | } 6 | 7 | return val + multiple - rest; 8 | } 9 | -------------------------------------------------------------------------------- /packages/personalidol/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/personalidol/README.md: -------------------------------------------------------------------------------- 1 | # `personalidol` 2 | 3 | Domain logic for PersonalIdol game. It does not contain the server etc, just 4 | the frontend game implementation details. 5 | -------------------------------------------------------------------------------- /packages/personalidol/src/BuildGeometryAttributesCallback.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "../../quakemaps/src/Brush.type"; 2 | import type { Geometry } from "../../quakemaps/src/Geometry.type"; 3 | 4 | export type BuildGeometryAttributesCallback = (brushes: Array) => Geometry; 5 | -------------------------------------------------------------------------------- /packages/personalidol/src/CameraParameters.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CameraParameters { 2 | ZOOM_DEFAULT = 201, 3 | ZOOM_MAX = 1, 4 | ZOOM_MIN = 1401, 5 | // When zoom increments are discrete, like when using mouse scroll wheel. 6 | ZOOM_STEP = 50, 7 | // When zoom is fluent and changes every frame, for example when using 8 | // keyboard. 9 | ZOOM_VELOCITY = 3500, 10 | } 11 | -------------------------------------------------------------------------------- /packages/personalidol/src/CharacterView.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import type { AnyEntity } from "./AnyEntity.type"; 4 | import type { CharacterViewState } from "./CharacterViewState.type"; 5 | import type { EntityView } from "./EntityView.interface"; 6 | 7 | export interface CharacterView extends EntityView { 8 | readonly isCharacterView: true; 9 | readonly state: CharacterViewState; 10 | 11 | transitionBy(vec: Vector3): void; 12 | 13 | transitionTo(vec: Vector3): void; 14 | } 15 | -------------------------------------------------------------------------------- /packages/personalidol/src/CharacterViewState.type.ts: -------------------------------------------------------------------------------- 1 | import type { EntityViewState } from "./EntityViewState.type"; 2 | 3 | export type CharacterViewState = EntityViewState & { 4 | animation: "jump" | "run" | "stand"; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/personalidol/src/DOMBreakpoints.enum.ts: -------------------------------------------------------------------------------- 1 | export const enum DOMBreakpoints { 2 | MobileMax = 1023, 3 | TabletMin = 1024, 4 | TabletMax = 1279, 5 | DesktopMin = 1280, 6 | } 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/DOMElementViewContext.type.ts: -------------------------------------------------------------------------------- 1 | import type { DOMElementViewContext as BaseDOMElementViewContext } from "../../dom-renderer/src/DOMElementViewContext.type"; 2 | 3 | import type { UserSettings } from "./UserSettings.type"; 4 | 5 | export type DOMElementViewContext = BaseDOMElementViewContext & { 6 | dimensionsState: Uint32Array; 7 | gameMessagePort: MessagePort; 8 | keyboardState: Uint8Array; 9 | mouseState: Int32Array; 10 | touchState: Int32Array; 11 | uiMessagePort: MessagePort; 12 | userSettings: UserSettings; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/personalidol/src/DOMZIndex.enum.ts: -------------------------------------------------------------------------------- 1 | export const enum DOMZIndex { 2 | // CSS2DRenderer sets z-index of elements dynamically. It is easier to reserve 3 | // low z-index for dynamic elements than to try to mix them in with manaully 4 | // managed ones. 5 | __RESERVED_CSS2DRENDERER = 10000, 6 | 7 | // Order here is important, settings z-index of every possible element 8 | // prevents overlapping. Later ones on this list will end on top of lower 9 | // ones. 10 | InGameObjectLabel, 11 | InGameMousePointerGadgets, 12 | InGameMenuTrigger, 13 | MainMenu, 14 | Settings, 15 | ProgressManagerState, 16 | StatsReporter, 17 | MousePointer, 18 | } 19 | -------------------------------------------------------------------------------- /packages/personalidol/src/Entity.type.ts: -------------------------------------------------------------------------------- 1 | import type { EntityProperties } from "../../quakemaps/src/EntityProperties.type"; 2 | 3 | export type Entity = { 4 | readonly id: string; 5 | readonly classname: string; 6 | readonly properties: EntityProperties; 7 | readonly transferables: Array; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityControllerBagState.type.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableState } from "../../framework/src/DisposableState.type"; 2 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 3 | import type { MountableState } from "../../framework/src/MountableState.type"; 4 | import type { PauseableState } from "../../framework/src/PauseableState.type"; 5 | import type { PreloadableState } from "../../framework/src/PreloadableState.type"; 6 | 7 | export type EntityControllerBagState = DisposableState & 8 | MainLoopUpdatableState & 9 | MountableState & 10 | PauseableState & 11 | PreloadableState; 12 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityControllerFactory.interface.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | import type { EntityController } from "./EntityController.interface"; 3 | import type { EntityView } from "./EntityView.interface"; 4 | 5 | export interface EntityControllerFactory { 6 | readonly isEntityControllerFactory: true; 7 | 8 | create(entityView: EntityView): Generator>; 9 | } 10 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityControllerState.type.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableState } from "../../framework/src/DisposableState.type"; 2 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 3 | import type { MountableState } from "../../framework/src/MountableState.type"; 4 | import type { PauseableState } from "../../framework/src/PauseableState.type"; 5 | import type { PreloadableState } from "../../framework/src/PreloadableState.type"; 6 | 7 | export type EntityControllerState = DisposableState & 8 | MainLoopUpdatableState & 9 | MountableState & 10 | PauseableState & 11 | PreloadableState; 12 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityFuncGroup.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "../../quakemaps/src/Brush.type"; 2 | import type { Geometry } from "../../quakemaps/src/Geometry.type"; 3 | 4 | import type { Entity } from "./Entity.type"; 5 | 6 | export type EntityFuncGroup = Geometry & 7 | Entity & { 8 | readonly brushes: Array; 9 | readonly classname: "func_group"; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityGLTFModel.type.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3Simple } from "../../quakemaps/src/Vector3Simple.type"; 2 | 3 | import type { Entity } from "./Entity.type"; 4 | 5 | export type EntityGLTFModel = Entity & { 6 | readonly angle: number; 7 | readonly classname: "model_gltf"; 8 | readonly model_filename: string; 9 | readonly model_name: string; 10 | readonly model_texture: string; 11 | readonly origin: Vector3Simple; 12 | readonly scale: number; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityLight.type.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3Simple } from "../../quakemaps/src/Vector3Simple.type"; 2 | 3 | import type { Entity } from "./Entity.type"; 4 | 5 | export type EntityLight = Entity & { 6 | readonly color: string; 7 | readonly decay: number; 8 | readonly intensity: number; 9 | readonly origin: Vector3Simple; 10 | readonly quality_map: number; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityLightAmbient.type.ts: -------------------------------------------------------------------------------- 1 | import type { Entity } from "./Entity.type"; 2 | 3 | export type EntityLightAmbient = Entity & { 4 | readonly classname: "light_ambient"; 5 | readonly light: number; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityLightHemisphere.type.ts: -------------------------------------------------------------------------------- 1 | import type { Entity } from "./Entity.type"; 2 | 3 | export type EntityLightHemisphere = Entity & { 4 | readonly classname: "light_hemisphere"; 5 | readonly light: number; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityLightPoint.type.ts: -------------------------------------------------------------------------------- 1 | import type { EntityLight } from "./EntityLight.type"; 2 | 3 | export type EntityLightPoint = EntityLight & { 4 | readonly classname: "light_point"; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityLightSpotlight.type.ts: -------------------------------------------------------------------------------- 1 | import type { EntityLight } from "./EntityLight.type"; 2 | 3 | export type EntityLightSpotlight = EntityLight & { 4 | readonly classname: "light_spotlight"; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityLookupCallback.type.ts: -------------------------------------------------------------------------------- 1 | import type { Texture as ITexture } from "three"; 2 | 3 | import type { AnyEntity } from "./AnyEntity.type"; 4 | import type { EntityLookup } from "./EntityLookup.type"; 5 | import type { EntityView } from "./EntityView.interface"; 6 | 7 | export type EntityLookupCallback = ( 8 | entity: E, 9 | worldspawnTexture: ITexture, 10 | targetedViews: Set> 11 | ) => EntityView; 12 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityLookupTable.type.ts: -------------------------------------------------------------------------------- 1 | import type { EntityLookup } from "./EntityLookup.type"; 2 | import type { EntityLookupCallback } from "./EntityLookupCallback.type"; 3 | 4 | export type EntityLookupTable = { 5 | [K in keyof EntityLookup]: EntityLookupCallback; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityPlayer.type.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3Simple } from "../../quakemaps/src/Vector3Simple.type"; 2 | 3 | import type { Entity } from "./Entity.type"; 4 | 5 | export type EntityPlayer = Entity & { 6 | readonly classname: "player"; 7 | readonly origin: Vector3Simple; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityScriptedBrush.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "../../quakemaps/src/Brush.type"; 2 | import type { Geometry } from "../../quakemaps/src/Geometry.type"; 3 | 4 | import type { Entity } from "./Entity.type"; 5 | 6 | export type EntityScriptedBrush = Geometry & 7 | Entity & { 8 | readonly brushes: Array; 9 | readonly classname: "scripted_brush"; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityScriptedZone.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "../../quakemaps/src/Brush.type"; 2 | import type { Geometry } from "../../quakemaps/src/Geometry.type"; 3 | 4 | import type { Entity } from "./Entity.type"; 5 | 6 | export type EntityScriptedZone = Geometry & 7 | Entity & { 8 | readonly brushes: Array; 9 | readonly classname: "scripted_zone"; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntitySounds.type.ts: -------------------------------------------------------------------------------- 1 | import type { Entity } from "./Entity.type"; 2 | 3 | export type EntitySounds = Entity & { 4 | readonly classname: "sounds"; 5 | readonly sounds: string; 6 | readonly transferables: []; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntitySparkParticles.type.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3Simple } from "../../quakemaps/src/Vector3Simple.type"; 2 | 3 | import type { Entity } from "./Entity.type"; 4 | 5 | export type EntitySparkParticles = Entity & { 6 | readonly classname: "spark_particles"; 7 | readonly origin: Vector3Simple; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityTarget.type.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3Simple } from "../../quakemaps/src/Vector3Simple.type"; 2 | 3 | import type { Entity } from "./Entity.type"; 4 | 5 | export type EntityTarget = Entity & { 6 | readonly classname: "target"; 7 | readonly origin: Vector3Simple; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityView.interface.ts: -------------------------------------------------------------------------------- 1 | import type { View } from "../../views/src/View.interface"; 2 | 3 | import type { AnyEntity } from "./AnyEntity.type"; 4 | import type { EntityViewState } from "./EntityViewState.type"; 5 | 6 | export interface EntityView extends View { 7 | readonly state: EntityViewState; 8 | 9 | entity: E; 10 | isEntityView: true; 11 | isExpectingTargets: boolean; 12 | } 13 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityViewFactory.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Texture as ITexture } from "three/src/textures/Texture"; 2 | 3 | import type { AnyEntity } from "./AnyEntity.type"; 4 | import type { EntityLookup } from "./EntityLookup.type"; 5 | import type { EntityView } from "./EntityView.interface"; 6 | 7 | export interface EntityViewFactory { 8 | readonly isEntityViewFactory: true; 9 | 10 | create( 11 | entity: EntityLookup[K], 12 | targetedViews: Set>, 13 | worldspawnTexture: ITexture 14 | ): EntityView; 15 | } 16 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityViewState.type.ts: -------------------------------------------------------------------------------- 1 | import type { ViewState } from "../../views/src/ViewState.type"; 2 | 3 | export type EntityViewState = ViewState & { 4 | isObscuring: boolean; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityWithController.type.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | 3 | export type EntityWithController = AnyEntity & { 4 | properties: { 5 | controller: string; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityWithObjectLabel.type.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | 3 | export type EntityWithObjectLabel = AnyEntity & { 4 | properties: { 5 | label: string; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/EntityWorldspawn.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "../../quakemaps/src/Brush.type"; 2 | import type { Geometry } from "../../quakemaps/src/Geometry.type"; 3 | 4 | import type { Entity } from "./Entity.type"; 5 | 6 | export type EntityWorldspawn = Entity & 7 | Geometry & { 8 | readonly brushes: Array; 9 | readonly classname: "worldspawn"; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/personalidol/src/GameState.ts: -------------------------------------------------------------------------------- 1 | import { GameState as IGameState } from "./GameState.type"; 2 | 3 | function createEmptyState(): IGameState { 4 | return { 5 | currentLocationMap: null, 6 | currentWorldMap: null, 7 | isScenePaused: false, 8 | previousLocationMap: null, 9 | previousWorldMap: null, 10 | }; 11 | } 12 | 13 | export const GameState = Object.freeze({ 14 | createEmptyState: createEmptyState, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/personalidol/src/GameState.type.ts: -------------------------------------------------------------------------------- 1 | export type GameState = { 2 | currentLocationMap: null | string; 3 | currentWorldMap: null | string; 4 | isScenePaused: boolean; 5 | previousLocationMap: null | string; 6 | previousWorldMap: null | string; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/GameStateController.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | import type { GameState } from "./GameState.type"; 5 | 6 | export interface GameStateController extends MainLoopUpdatable, Service { 7 | gameState: GameState; 8 | } 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/GeometryWithBrushesEntity.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "../../quakemaps/src/Brush.type"; 2 | import type { Geometry } from "../../quakemaps/src/Geometry.type"; 3 | 4 | import type { Entity } from "./Entity.type"; 5 | 6 | export type GeometryWithBrushesEntity = Entity & 7 | Geometry & { 8 | readonly brushes: Array; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/personalidol/src/InstancedGLTFModelViewManager.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Object3D } from "three/src/core/Object3D"; 2 | 3 | import type { View } from "../../views/src/View.interface"; 4 | 5 | import type { EntityGLTFModel } from "./EntityGLTFModel.type"; 6 | import type { InstancedMeshHandle } from "./InstancedMeshHandle.interface"; 7 | 8 | export interface InstancedGLTFModelViewManager extends View { 9 | readonly isInstancedGLTFModelViewManager: true; 10 | 11 | createEntiyMeshHandle(entity: EntityGLTFModel, reference: Object3D): Promise; 12 | 13 | expectEntity(entity: EntityGLTFModel): void; 14 | } 15 | -------------------------------------------------------------------------------- /packages/personalidol/src/InstancedMeshHandle.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Object3D } from "three/src/core/Object3D"; 2 | 3 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 4 | import type { Preloadable } from "../../framework/src/Preloadable.interface"; 5 | 6 | import type { InstancedMeshHandleState } from "./InstancedMeshHandleState.type"; 7 | 8 | export interface InstancedMeshHandle extends MainLoopUpdatable, Preloadable { 9 | readonly isInstancedMeshHandle: true; 10 | readonly object3D: Object3D; 11 | readonly state: InstancedMeshHandleState; 12 | } 13 | -------------------------------------------------------------------------------- /packages/personalidol/src/InstancedMeshHandleState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | import type { PreloadableState } from "../../framework/src/PreloadableState.type"; 3 | 4 | export type InstancedMeshHandleState = MainLoopUpdatableState & PreloadableState; 5 | -------------------------------------------------------------------------------- /packages/personalidol/src/LocationMapScene.interface.ts: -------------------------------------------------------------------------------- 1 | import type { PollablePreloading } from "../../framework/src/PollablePreloading.interface"; 2 | import type { Scene } from "../../framework/src/Scene.interface"; 3 | 4 | export interface LocationMapScene extends PollablePreloading, Scene { 5 | readonly currentMap: string; 6 | readonly isLocationMapScene: true; 7 | } 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/MainMenuScene.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Scene } from "../../framework/src/Scene.interface"; 2 | 3 | export interface MainMenuScene extends Scene { 4 | readonly isMainMenuScene: true; 5 | } 6 | -------------------------------------------------------------------------------- /packages/personalidol/src/MessageGameStateChange.type.ts: -------------------------------------------------------------------------------- 1 | import { GameState } from "./GameState.type"; 2 | 3 | export type MessageGameStateChange = Partial; 4 | -------------------------------------------------------------------------------- /packages/personalidol/src/MessageUIStateChange.type.ts: -------------------------------------------------------------------------------- 1 | import { UIState } from "./UIState.type"; 2 | 3 | export type MessageUIStateChange = Partial; 4 | -------------------------------------------------------------------------------- /packages/personalidol/src/NPCEntity.type.ts: -------------------------------------------------------------------------------- 1 | import type { EntityGLTFModel } from "./EntityGLTFModel.type"; 2 | import type { EntityPlayer } from "./EntityPlayer.type"; 3 | 4 | // prettier-ignore 5 | export type NPCEntity = 6 | | EntityGLTFModel 7 | | EntityPlayer 8 | ; 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/NPCEntityController.interface.ts: -------------------------------------------------------------------------------- 1 | import type { RigidBodyRemoteHandle } from "../../dynamics/src/RigidBodyRemoteHandle.interface"; 2 | 3 | import type { EntityController } from "./EntityController.interface"; 4 | import type { NPCEntity } from "./NPCEntity.type"; 5 | 6 | export interface NPCEntityController extends EntityController { 7 | rigidBody: RigidBodyRemoteHandle; 8 | } 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/PlayerEntityController.interface.ts: -------------------------------------------------------------------------------- 1 | import type { PollablePreloading } from "../../framework/src/PollablePreloading.interface"; 2 | 3 | import type { EntityController } from "./EntityController.interface"; 4 | import type { EntityPlayer } from "./EntityPlayer.type"; 5 | 6 | export interface PlayerEntityController extends EntityController, PollablePreloading {} 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/ReloadButtonDOMElementView.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | import { DOMElementView } from "../../dom-renderer/src/DOMElementView"; 4 | 5 | import type { DOMElementViewContext } from "./DOMElementViewContext.type"; 6 | 7 | export class ReloadButtonDOMElementView extends DOMElementView { 8 | constructor() { 9 | super(); 10 | 11 | this.onClick = this.onClick.bind(this); 12 | } 13 | 14 | onClick(evt: MouseEvent) { 15 | evt.preventDefault(); 16 | 17 | window.location.reload(); 18 | } 19 | 20 | render(delta: number) { 21 | return {this.t("ui:reload_button_reload_now")}; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/personalidol/src/SimulantsLookup.type.ts: -------------------------------------------------------------------------------- 1 | import type { Simulant } from "../../dynamics/src/Simulant.interface"; 2 | import type { SimulantsLookup as BaseSimulantsLookup } from "../../dynamics/src/SimulantsLookup.type"; 3 | 4 | export type SimulantsLookup = BaseSimulantsLookup & { 5 | npc: Simulant; 6 | "worldspawn-geoemetry": Simulant; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/TargetOrTargetedEntity.type.ts: -------------------------------------------------------------------------------- 1 | import type { TargetedEntity } from "./TargetedEntity.type"; 2 | import type { TargetingEntity } from "./TargetingEntity.type"; 3 | 4 | export type TargetOrTargetedEntity = TargetedEntity | TargetingEntity; 5 | -------------------------------------------------------------------------------- /packages/personalidol/src/TargetedEntity.type.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | 3 | export type TargetedEntity = AnyEntity & { 4 | properties: { 5 | targetname: string; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/TargetingEntity.type.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | 3 | export type TargetingEntity = AnyEntity & { 4 | properties: { 5 | target: string; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/UIState.ts: -------------------------------------------------------------------------------- 1 | import { UIState as IUIState } from "./UIState.type"; 2 | 3 | function createEmptyState(): IUIState { 4 | return { 5 | isInGameMenuOpened: false, 6 | isInGameMenuTriggerVisible: false, 7 | isLanguageSettingsScreenOpened: false, 8 | isMousePointerLayerVisible: false, 9 | isUserSettingsScreenOpened: false, 10 | isVirtualJoystickLayerVisible: false, 11 | }; 12 | } 13 | 14 | export const UIState = Object.freeze({ 15 | createEmptyState: createEmptyState, 16 | }); 17 | -------------------------------------------------------------------------------- /packages/personalidol/src/UIState.type.ts: -------------------------------------------------------------------------------- 1 | export type UIState = { 2 | isInGameMenuOpened: boolean; 3 | isInGameMenuTriggerVisible: boolean; 4 | isLanguageSettingsScreenOpened: boolean; 5 | isMousePointerLayerVisible: boolean; 6 | isUserSettingsScreenOpened: boolean; 7 | isVirtualJoystickLayerVisible: boolean; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/UIStateController.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | import type { UIState } from "./UIState.type"; 5 | import type { UIStateControllerInfo } from "./UIStateControllerInfo.type"; 6 | 7 | export interface UIStateController extends MainLoopUpdatable, Service { 8 | readonly info: UIStateControllerInfo; 9 | readonly uiState: UIState; 10 | } 11 | -------------------------------------------------------------------------------- /packages/personalidol/src/UIStateControllerInfo.type.ts: -------------------------------------------------------------------------------- 1 | export type UIStateControllerInfo = {}; 2 | -------------------------------------------------------------------------------- /packages/personalidol/src/UIStateControllerStatsReport.type.ts: -------------------------------------------------------------------------------- 1 | import type { StatsReport } from "../../framework/src/StatsReport.type"; 2 | 3 | export type UIStateControllerStatsReport = StatsReport & {}; 4 | -------------------------------------------------------------------------------- /packages/personalidol/src/UserSettings.type.ts: -------------------------------------------------------------------------------- 1 | import type { OrthographicCamera } from "three/src/cameras/OrthographicCamera"; 2 | import type { PerspectiveCamera } from "three/src/cameras/PerspectiveCamera"; 3 | 4 | import type { UserSettings as BaseUserSettings } from "../../framework/src/UserSettings.type"; 5 | 6 | export type UserSettings = BaseUserSettings & { 7 | cameraZoomAmount: number; 8 | cameraType: OrthographicCamera["type"] | PerspectiveCamera["type"]; 9 | devicePixelRatio: number; 10 | dynamicLightQuality: 0 | 1 | 2 | 4; 11 | language: string; 12 | pixelRatio: number; 13 | shadowMapSize: 512 | 1024 | 2048 | 4096; 14 | useOffscreenCanvas: boolean; 15 | useShadows: boolean; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/personalidol/src/UserSettingsDynamicLightQualityMap.enum.ts: -------------------------------------------------------------------------------- 1 | export enum UserSettingsDynamicLightQualityMap { 2 | None = 0, 3 | Low = 1, 4 | Medium = 2, 5 | High = 4, 6 | } 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/ViewBuildingStep.type.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | import type { TargetedEntity } from "./TargetedEntity.type"; 3 | 4 | export type ViewBuildingStep = { 5 | entity: AnyEntity; 6 | targetedEntities: ReadonlyArray; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/WorldMapScene.interface.ts: -------------------------------------------------------------------------------- 1 | import type { PollablePreloading } from "../../framework/src/PollablePreloading.interface"; 2 | import type { Scene } from "../../framework/src/Scene.interface"; 3 | 4 | export interface WorldMapScene extends PollablePreloading, Scene { 5 | readonly currentMap: string; 6 | readonly isLocationMapScene: true; 7 | } 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/createEntityControllerState.ts: -------------------------------------------------------------------------------- 1 | import type { EntityControllerState } from "./EntityControllerState.type"; 2 | 3 | export function createEntityControllerState( 4 | entityControllerState: Partial = {} 5 | ): EntityControllerState { 6 | return Object.seal( 7 | Object.assign( 8 | { 9 | isDisposed: false, 10 | isMounted: false, 11 | isPaused: false, 12 | isPreloaded: false, 13 | isPreloading: false, 14 | needsUpdates: false, 15 | }, 16 | entityControllerState 17 | ) 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/personalidol/src/createEntityViewState.ts: -------------------------------------------------------------------------------- 1 | import { createViewState } from "../../views/src/createViewState"; 2 | 3 | import type { EntityViewState } from "./EntityViewState.type"; 4 | 5 | export function createEntityViewState(entityViewState: Partial = {}): EntityViewState { 6 | return Object.seal( 7 | Object.assign( 8 | {}, 9 | createViewState(), 10 | { 11 | isObscuring: false, 12 | }, 13 | entityViewState 14 | ) 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/personalidol/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __ASSETS_BASE_PATH: string; 2 | declare const __CACHE_BUST: string; 3 | -------------------------------------------------------------------------------- /packages/personalidol/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare const __ASSETS_BASE_PATH: string; 2 | declare const __CACHE_BUST: string; 3 | -------------------------------------------------------------------------------- /packages/personalidol/src/isCharacterView.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | import type { CharacterView } from "./CharacterView.interface"; 3 | import type { EntityView } from "./EntityView.interface"; 4 | 5 | export function isCharacterView(view: EntityView): view is CharacterView { 6 | return true === (view as CharacterView).isCharacterView; 7 | } 8 | -------------------------------------------------------------------------------- /packages/personalidol/src/isEntityOfClass.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | 3 | export function isEntityOfClass( 4 | entity: AnyEntity, 5 | classname: C 6 | ): entity is E { 7 | return classname === entity.classname; 8 | } 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/isEntityView.ts: -------------------------------------------------------------------------------- 1 | import type { View } from "../../views/src/View.interface"; 2 | 3 | import type { AnyEntity } from "./AnyEntity.type"; 4 | import type { EntityView } from "./EntityView.interface"; 5 | 6 | export function isEntityView(view: View): view is EntityView { 7 | return (view as EntityView).isEntityView; 8 | } 9 | -------------------------------------------------------------------------------- /packages/personalidol/src/isEntityViewOfClass.ts: -------------------------------------------------------------------------------- 1 | import { isEntityOfClass } from "./isEntityOfClass"; 2 | 3 | import type { AnyEntity } from "./AnyEntity.type"; 4 | import type { EntityView } from "./EntityView.interface"; 5 | 6 | export function isEntityViewOfClass( 7 | entityView: EntityView, 8 | classname: C 9 | ): entityView is EntityView { 10 | return isEntityOfClass(entityView.entity, classname); 11 | } 12 | -------------------------------------------------------------------------------- /packages/personalidol/src/isEntityWithController.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | import type { EntityWithController } from "./EntityWithController.type"; 3 | 4 | export function isEntityWithController(entity: AnyEntity): entity is EntityWithController { 5 | return "string" === typeof entity.properties.controller; 6 | } 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/isEntityWithObjectLabel.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | import type { EntityWithObjectLabel } from "./EntityWithObjectLabel.type"; 3 | 4 | export function isEntityWithObjectLabel(entity: AnyEntity): entity is EntityWithObjectLabel { 5 | return "string" === typeof entity.properties.label; 6 | } 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/isMainMenuScene.ts: -------------------------------------------------------------------------------- 1 | import type { Scene } from "../../framework/src/Scene.interface"; 2 | 3 | import type { MainMenuScene } from "./MainMenuScene.interface"; 4 | 5 | export function isMainMenuScene(scene: null | Scene): scene is MainMenuScene { 6 | if (null === scene) { 7 | return false; 8 | } 9 | 10 | return true === (scene as MainMenuScene).isMainMenuScene; 11 | } 12 | -------------------------------------------------------------------------------- /packages/personalidol/src/isNPCEntityView.ts: -------------------------------------------------------------------------------- 1 | import { isEntityViewOfClass } from "./isEntityViewOfClass"; 2 | 3 | import type { AnyEntity } from "./AnyEntity.type"; 4 | import type { EntityGLTFModel } from "./EntityGLTFModel.type"; 5 | import type { EntityPlayer } from "./EntityPlayer.type"; 6 | import type { EntityView } from "./EntityView.interface"; 7 | import type { NPCEntity } from "./NPCEntity.type"; 8 | 9 | export function isNPCEntityView(view: EntityView): view is EntityView { 10 | return isEntityViewOfClass(view, "player") || isEntityViewOfClass(view, "model_gltf"); 11 | } 12 | -------------------------------------------------------------------------------- /packages/personalidol/src/isTarget.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | import type { TargetedEntity } from "./TargetedEntity.type"; 3 | 4 | export function isTarget(entity: AnyEntity): entity is TargetedEntity { 5 | return entity.properties.hasOwnProperty("targetname"); 6 | } 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/isTargetedBy.ts: -------------------------------------------------------------------------------- 1 | import type { TargetedEntity } from "./TargetedEntity.type"; 2 | import type { TargetingEntity } from "./TargetingEntity.type"; 3 | 4 | export function isTargetedBy(target: TargetedEntity, targetedBy: TargetingEntity): boolean { 5 | return target.properties.targetname === targetedBy.properties.target; 6 | } 7 | -------------------------------------------------------------------------------- /packages/personalidol/src/isTargeting.ts: -------------------------------------------------------------------------------- 1 | import type { AnyEntity } from "./AnyEntity.type"; 2 | import type { TargetingEntity } from "./TargetingEntity.type"; 3 | 4 | export function isTargeting(entity: AnyEntity): entity is TargetingEntity { 5 | return entity.properties.hasOwnProperty("target"); 6 | } 7 | -------------------------------------------------------------------------------- /packages/quakemaps/.gitignore: -------------------------------------------------------------------------------- 1 | /fixtures/autosave 2 | -------------------------------------------------------------------------------- /packages/quakemaps/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/quakemaps/README.md: -------------------------------------------------------------------------------- 1 | # `quakemaps` 2 | 3 | Parser that is able to process quake `.map` files and generate geometry 4 | attributes from them. 5 | -------------------------------------------------------------------------------- /packages/quakemaps/fixtures/map-test.map: -------------------------------------------------------------------------------- 1 | // Game: PersonalIdol 2 | // Format: Standard 3 | // entity 0 4 | { 5 | "classname" "worldspawn" 6 | // brush 0 7 | { 8 | ( -64 -64 -16 ) ( -64 -63 -16 ) ( -64 -64 -15 ) __TB_empty 0 0 0 1 1 9 | ( -64 -64 -16 ) ( -64 -64 -15 ) ( -63 -64 -16 ) __TB_empty 0 0 0 1 1 10 | ( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) __TB_empty 0 0 0 1 1 11 | ( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) __TB_empty 0 0 0 1 1 12 | ( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) __TB_empty 0 0 0 1 1 13 | ( 64 64 16 ) ( 64 64 17 ) ( 64 65 16 ) __TB_empty 0 0 0 1 1 14 | } 15 | } 16 | // entity 1 17 | { 18 | "classname" "player" 19 | "origin" "-32 -32 40" 20 | "foo" "bar\"baz\"booz" 21 | } 22 | // entity 2 23 | { 24 | "classname" "light_point" 25 | "color" "FFC000" 26 | "decay" "2" 27 | "origin" "16 -32 40" 28 | "intensity" "255" 29 | } 30 | -------------------------------------------------------------------------------- /packages/quakemaps/src/Brush.type.ts: -------------------------------------------------------------------------------- 1 | import type { HalfSpace } from "./HalfSpace.type"; 2 | 3 | export type Brush = { 4 | halfSpaces: HalfSpace[]; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/quakemaps/src/BrushHalfSpaceTriangle.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "./Brush.type"; 2 | import type { HalfSpace } from "./HalfSpace.type"; 3 | import type { TriangleSimple } from "./TriangleSimple.type"; 4 | 5 | export type BrushHalfSpaceTriangle = { 6 | brush: Brush; 7 | halfSpace: HalfSpace; 8 | triangle: TriangleSimple; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/quakemaps/src/EntityProperties.type.ts: -------------------------------------------------------------------------------- 1 | export type EntityProperties = { 2 | [key: string]: string; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/quakemaps/src/EntitySketch.type.ts: -------------------------------------------------------------------------------- 1 | import type { Brush } from "./Brush.type"; 2 | import type { EntityProperties } from "./EntityProperties.type"; 3 | 4 | export type EntitySketch = { 5 | brushes: Brush[]; 6 | properties: EntityProperties; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/quakemaps/src/EntitySketchProperty.type.ts: -------------------------------------------------------------------------------- 1 | export type EntitySketchProperty = [string, string]; 2 | -------------------------------------------------------------------------------- /packages/quakemaps/src/EntityType.type.ts: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | export type EntityType = 3 | | null 4 | | "_tb_group" 5 | | "_tb_layer" 6 | ; 7 | -------------------------------------------------------------------------------- /packages/quakemaps/src/Exception.ts: -------------------------------------------------------------------------------- 1 | export class Exception extends Error {} 2 | -------------------------------------------------------------------------------- /packages/quakemaps/src/Geometry.type.ts: -------------------------------------------------------------------------------- 1 | export type Geometry = { 2 | readonly atlasUVStart: Float32Array; 3 | readonly atlasUVStop: Float32Array; 4 | readonly index: Uint32Array; 5 | readonly normal: Float32Array; 6 | readonly position: Float32Array; 7 | readonly transferables: Array; 8 | readonly uv: Float32Array; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/quakemaps/src/HalfSpace.type.ts: -------------------------------------------------------------------------------- 1 | import type { Plane } from "three"; 2 | import type { Vector3 } from "three"; 3 | 4 | export type HalfSpace = { 5 | coplanarPoints: [Vector3, Vector3, Vector3]; 6 | points: Array; 7 | plane: Plane; 8 | texture: { 9 | name: string; 10 | offset: { 11 | x: number; 12 | y: number; 13 | }; 14 | rotation: number; 15 | scale: { 16 | x: number; 17 | y: number; 18 | }; 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/quakemaps/src/IntersectingPointsCache.type.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3 } from "three"; 2 | 3 | export type IntersectingPointsCache = { 4 | [key: string]: Vector3; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/quakemaps/src/ParserException.ts: -------------------------------------------------------------------------------- 1 | export class ParserException extends Error { 2 | constructor(filename: string, line: number, message: string) { 3 | super(`${filename}:${line} - ${message}`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/quakemaps/src/TextureDimensionsResolver.type.ts: -------------------------------------------------------------------------------- 1 | import type { AtlasTextureDimension } from "../../texture-loader/src/AtlasTextureDimension.type"; 2 | 3 | export type TextureDimensionsResolver = (textureName: string) => AtlasTextureDimension; 4 | -------------------------------------------------------------------------------- /packages/quakemaps/src/TextureUrlResolver.type.ts: -------------------------------------------------------------------------------- 1 | export type TextureUrlResolver = (textureName: string) => string; 2 | -------------------------------------------------------------------------------- /packages/quakemaps/src/TriangleSimple.type.ts: -------------------------------------------------------------------------------- 1 | import type { Vector3Simple } from "./Vector3Simple.type"; 2 | 3 | export type TriangleSimple = [Vector3Simple, Vector3Simple, Vector3Simple]; 4 | -------------------------------------------------------------------------------- /packages/quakemaps/src/UnmarshalException.ts: -------------------------------------------------------------------------------- 1 | import { Exception } from "./Exception"; 2 | 3 | export class UnmarshalException extends Exception { 4 | constructor(filename: string, line: number, message: string) { 5 | super(`${filename}:${line} - ${message}`); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/quakemaps/src/Vector3Simple.type.ts: -------------------------------------------------------------------------------- 1 | export type Vector3Simple = { 2 | x: number; 3 | y: number; 4 | z: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/quakemaps/src/buildGeometryPoints.ts: -------------------------------------------------------------------------------- 1 | import { buildGeometryTriangles } from "./buildGeometryTriangles"; 2 | import { marshalCoords } from "./marshalCoords"; 3 | 4 | import type { Brush } from "./Brush.type"; 5 | import type { Vector3Simple } from "./Vector3Simple.type"; 6 | 7 | export function* buildGeometryPoints(brushes: ReadonlyArray): Generator { 8 | const unique: Set = new Set(); 9 | 10 | for (let brushHalfSpaceTriangle of buildGeometryTriangles(brushes)) { 11 | for (let point of brushHalfSpaceTriangle.triangle) { 12 | const marshaled = marshalCoords(point.x, point.y, point.z); 13 | 14 | if (!unique.has(marshaled)) { 15 | unique.add(marshaled); 16 | 17 | yield point; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/quakemaps/src/fixBrushAfterDeserialization.ts: -------------------------------------------------------------------------------- 1 | import { Plane } from "three/src/math/Plane"; 2 | import { Vector3 } from "three/src/math/Vector3"; 3 | 4 | import type { Brush } from "../../quakemaps/src/Brush.type"; 5 | 6 | export function fixBrushAfterDeserialization(brush: Brush): void { 7 | for (let halfSpace of brush.halfSpaces) { 8 | // Sometimes, but not always, the object prototype is messed up after 9 | // serialization / deserialization. 10 | if (!halfSpace.plane.isPlane) { 11 | halfSpace.plane = new Plane( 12 | new Vector3(halfSpace.plane.normal.x, halfSpace.plane.normal.y, halfSpace.plane.normal.z), 13 | halfSpace.plane.constant 14 | ); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/quakemaps/src/getCenterOfMass.test.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import { getCenterOfMass } from "./getCenterOfMass"; 4 | 5 | test("finds center of mass", function () { 6 | const points = [new Vector3(0, 0, 0), new Vector3(0, 128, 0), new Vector3(128, 0, 0), new Vector3(128, 128, 0)]; 7 | 8 | const centerOfMass = getCenterOfMass(points); 9 | 10 | expect(centerOfMass.equals(new Vector3(64, 64, 0))).toBe(true); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/quakemaps/src/getCenterOfMass.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import type { Vector3 as IVector3 } from "three"; 4 | 5 | export function getCenterOfMass(points: ReadonlyArray): IVector3 { 6 | if (points.length < 2) { 7 | throw new Error("Can't get center of mass of less than two points."); 8 | } 9 | 10 | const acc = new Vector3(0, 0, 0); 11 | 12 | for (let i = 0; i < points.length; i += 1) { 13 | acc.add(points[i]); 14 | } 15 | 16 | return acc.divideScalar(points.length); 17 | } 18 | -------------------------------------------------------------------------------- /packages/quakemaps/src/isAlmostEqual.ts: -------------------------------------------------------------------------------- 1 | export function isAlmostEqual(value: number, compare: number, delta: number = 0.01): boolean { 2 | return value > compare - delta && value < compare + delta; 3 | } 4 | -------------------------------------------------------------------------------- /packages/quakemaps/src/isEmptyString.ts: -------------------------------------------------------------------------------- 1 | export function isEmptyString(str: string): boolean { 2 | return str.trim().length < 1; 3 | } 4 | -------------------------------------------------------------------------------- /packages/quakemaps/src/marshalCoords.ts: -------------------------------------------------------------------------------- 1 | export function marshalCoords(x: number, y: number, z: number): string { 2 | return `${z} ${x} ${y}`; 3 | } 4 | -------------------------------------------------------------------------------- /packages/quakemaps/src/marshalVector3.test.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import { marshalVector3 } from "./marshalVector3"; 4 | 5 | test("marshals Vector3 into string", function () { 6 | const vector3 = new Vector3(-32, 40, 16); 7 | 8 | expect(marshalVector3(vector3)).toBe("16 -32 40"); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/quakemaps/src/marshalVector3.ts: -------------------------------------------------------------------------------- 1 | import { marshalCoords } from "./marshalCoords"; 2 | 3 | import type { Vector3Simple } from "./Vector3Simple.type"; 4 | 5 | export function marshalVector3(vector: Vector3Simple): string { 6 | return marshalCoords(vector.x, vector.y, vector.z); 7 | } 8 | -------------------------------------------------------------------------------- /packages/quakemaps/src/sanitizeVector3.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import { UnmarshalException } from "./UnmarshalException"; 4 | 5 | import type { Vector3 as IVector3 } from "three"; 6 | 7 | export function sanitizeVector3( 8 | filename: string, 9 | lineno: number, 10 | inputX: string, 11 | inputY: string, 12 | inputZ: string 13 | ): IVector3 { 14 | const x = Number(inputX); 15 | const y = Number(inputY); 16 | const z = Number(inputZ); 17 | 18 | if (isNaN(x) || isNaN(y) || isNaN(y)) { 19 | throw new UnmarshalException(filename, lineno, "Point consists of invalid numbers."); 20 | } 21 | 22 | // .map files use different coordinates system than THREE, Oimo, etc 23 | // XYZ -> YZX 24 | return new Vector3(y, z, x); 25 | } 26 | -------------------------------------------------------------------------------- /packages/quakemaps/src/triangulateFacePoints.ts: -------------------------------------------------------------------------------- 1 | import { sortPointsCounterClockwise } from "./sortPointsCounterClockwise"; 2 | 3 | import type { Vector3 } from "three"; 4 | 5 | import type { TriangleSimple } from "./TriangleSimple.type"; 6 | 7 | export function* triangulateFacePoints( 8 | faceNormal: Vector3, 9 | pointsInput: ReadonlyArray 10 | ): Generator { 11 | if (pointsInput.length < 3) { 12 | throw new Error(`Can't triangulate 2, 1 or 0 points. Got ${pointsInput.length} points.`); 13 | } 14 | 15 | const points = sortPointsCounterClockwise(faceNormal, pointsInput); 16 | 17 | for (let i = 0; i < points.length - 2; i += 1) { 18 | yield [points[0], points[i + 1], points[i + 2]] as TriangleSimple; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/quakemaps/src/unmarshalVector3.test.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three/src/math/Vector3"; 2 | 3 | import { unmarshalVector3 } from "./unmarshalVector3"; 4 | 5 | test("vector3 is unmarshaled", async function () { 6 | const parsed = unmarshalVector3("test", 0, " 16 -32 40 "); 7 | 8 | expect(parsed.equals(new Vector3(-32, 40, 16))).toBe(true); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/quakemaps/src/unmarshalVector3.ts: -------------------------------------------------------------------------------- 1 | import { isEmptyString } from "./isEmptyString"; 2 | import { UnmarshalException } from "./UnmarshalException"; 3 | import { sanitizeVector3 } from "./sanitizeVector3"; 4 | 5 | import type { Vector3 } from "three"; 6 | 7 | const REGEXP_WHITESPACE = /\s+/; 8 | 9 | function rejectEmpty(str: string): boolean { 10 | return !isEmptyString(str); 11 | } 12 | 13 | export function unmarshalVector3(filename: string, lineno: number, marshaled: string): Vector3 { 14 | const parts = marshaled.split(REGEXP_WHITESPACE).filter(rejectEmpty); 15 | 16 | if (parts.length !== 3) { 17 | throw new UnmarshalException(filename, lineno, "Point must be defined by three numbers"); 18 | } 19 | 20 | return sanitizeVector3(filename, lineno, parts[0], parts[1], parts[2]); 21 | } 22 | -------------------------------------------------------------------------------- /packages/service-worker/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/service-worker/README.md: -------------------------------------------------------------------------------- 1 | # `service-worker` 2 | 3 | Wrapper around service worker to handle installation and some edge cases. 4 | -------------------------------------------------------------------------------- /packages/service-worker/src/ServiceWorkerManager.interface.ts: -------------------------------------------------------------------------------- 1 | export interface ServiceWorkerManager { 2 | install(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /packages/service-worker/src/ServiceWorkerManager.ts: -------------------------------------------------------------------------------- 1 | import type { Logger } from "loglevel"; 2 | 3 | import type { ServiceWorkerManager as IServiceWorkerManager } from "./ServiceWorkerManager.interface"; 4 | 5 | export function ServiceWorkerManager(logger: Logger, serviceWorkerFilename: string): IServiceWorkerManager { 6 | async function install() { 7 | await navigator.serviceWorker.register(serviceWorkerFilename); 8 | await navigator.serviceWorker.ready; 9 | } 10 | 11 | return { 12 | install: install, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /packages/texture-loader/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/texture-loader/README.md: -------------------------------------------------------------------------------- 1 | # `texture-loader` 2 | 3 | Loads textures either using `createImageBitmap` or via the main browser thread. 4 | It is designed to allow sending textures via `MessageChannel` and preferably 5 | loading textures in the worker thread. 6 | -------------------------------------------------------------------------------- /packages/texture-loader/src/Atlas.type.ts: -------------------------------------------------------------------------------- 1 | import type { AtlasTextureDimensions } from "./AtlasTextureDimensions.type"; 2 | 3 | export type Atlas = { 4 | imageData: ImageData; 5 | textureDimensions: AtlasTextureDimensions; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/texture-loader/src/AtlasResponse.type.ts: -------------------------------------------------------------------------------- 1 | import type { AtlasTextureDimensions } from "./AtlasTextureDimensions.type"; 2 | import type { ImageDataBufferResponse } from "./ImageDataBufferResponse.type"; 3 | 4 | export type AtlasResponse = ImageDataBufferResponse & { 5 | textureDimensions: AtlasTextureDimensions; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/texture-loader/src/AtlasService.interface.ts: -------------------------------------------------------------------------------- 1 | import type { RegistersMessagePort } from "../../framework/src/RegistersMessagePort.interface"; 2 | import type { Service } from "../../framework/src/Service.interface"; 3 | 4 | export interface AtlasService extends RegistersMessagePort, Service {} 5 | -------------------------------------------------------------------------------- /packages/texture-loader/src/AtlasTextureDimension.type.ts: -------------------------------------------------------------------------------- 1 | // I realize that the name of the type is not gramatically correct and 2 | // 'dimension' means only one dimension. 3 | // It is named like that to differentiate it from the 'AtlasTextureDimensions' 4 | // type. 5 | export type AtlasTextureDimension = { 6 | atlasLeft: number; 7 | atlasTop: number; 8 | height: number; 9 | uvStartU: number; 10 | uvStartV: number; 11 | uvStopU: number; 12 | uvStopV: number; 13 | width: number; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/texture-loader/src/AtlasTextureDimensions.type.ts: -------------------------------------------------------------------------------- 1 | import type { AtlasTextureDimension } from "./AtlasTextureDimension.type"; 2 | 3 | export type AtlasTextureDimensions = { 4 | [key: string]: AtlasTextureDimension; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/texture-loader/src/DOMTextureService.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | import type { RegistersMessagePort } from "../../framework/src/RegistersMessagePort.interface"; 3 | import type { Service } from "../../framework/src/Service.interface"; 4 | 5 | export interface DOMTextureService extends MainLoopUpdatable, RegistersMessagePort, Service {} 6 | -------------------------------------------------------------------------------- /packages/texture-loader/src/ImageBitmapResponse.type.ts: -------------------------------------------------------------------------------- 1 | import type { RPCMessage } from "../../framework/src/RPCMessage.type"; 2 | 3 | export type ImageBitmapResponse = RPCMessage & { 4 | imageBitmap: ImageBitmap; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/texture-loader/src/ImageDataBufferResponse.type.ts: -------------------------------------------------------------------------------- 1 | import type { RPCMessage } from "../../framework/src/RPCMessage.type"; 2 | 3 | export type ImageDataBufferResponse = RPCMessage & { 4 | imageDataBuffer: ArrayBuffer; 5 | imageDataHeight: number; 6 | imageDataWidth: number; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/texture-loader/src/TextureRequest.type.ts: -------------------------------------------------------------------------------- 1 | import type { RPCMessage } from "../../framework/src/RPCMessage.type"; 2 | 3 | export type TextureRequest = RPCMessage & { 4 | textureUrl: string; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/texture-loader/src/canvas2DDrawImage.ts: -------------------------------------------------------------------------------- 1 | export function canvas2DDrawImage( 2 | canvas: HTMLCanvasElement, 3 | context2D: CanvasRenderingContext2D, 4 | image: HTMLImageElement 5 | ) { 6 | canvas.height = image.naturalHeight; 7 | canvas.width = image.naturalWidth; 8 | 9 | // It's worth to note here that JS is asynchronous, but while we are in the 10 | // same thread, there is no risk of several images being written to the 11 | // canvas at the same time, so no locks are necessary. 12 | 13 | context2D.drawImage(image, 0, 0); 14 | } 15 | -------------------------------------------------------------------------------- /packages/texture-loader/src/imageDataBufferResponseToImageData.ts: -------------------------------------------------------------------------------- 1 | import type { ImageDataBufferResponse } from "./ImageDataBufferResponse.type"; 2 | 3 | export function imageDataBufferResponseToImageData({ 4 | imageDataBuffer, 5 | imageDataHeight, 6 | imageDataWidth, 7 | }: ImageDataBufferResponse) { 8 | return new ImageData(new Uint8ClampedArray(imageDataBuffer), imageDataWidth, imageDataHeight); 9 | } 10 | -------------------------------------------------------------------------------- /packages/texture-loader/src/imageDataBufferResponseToTexture.ts: -------------------------------------------------------------------------------- 1 | import { imageDataBufferResponseToImageData } from "./imageDataBufferResponseToImageData"; 2 | import { imageToTexture } from "./imageToTexture"; 3 | 4 | import type { Texture as ITexture } from "three"; 5 | 6 | import type { ImageDataBufferResponse } from "./ImageDataBufferResponse.type"; 7 | 8 | export function imageDataBufferResponseToTexture(response: ImageDataBufferResponse): ITexture { 9 | return imageToTexture(imageDataBufferResponseToImageData(response)); 10 | } 11 | -------------------------------------------------------------------------------- /packages/texture-loader/src/imageToTexture.ts: -------------------------------------------------------------------------------- 1 | import { NearestFilter, RepeatWrapping, RGBAFormat } from "three/src/constants"; 2 | 3 | import { CanvasTexture } from "three/src/textures/CanvasTexture"; 4 | 5 | import type { Texture as ITexture } from "three"; 6 | 7 | export function imageToTexture(image: ImageData | ImageBitmap): ITexture { 8 | // this typecasting is a hack to make it work with threejs 9 | const texture = new CanvasTexture(image as unknown as HTMLImageElement); 10 | 11 | texture.format = RGBAFormat; 12 | texture.wrapS = texture.wrapT = RepeatWrapping; 13 | texture.magFilter = texture.minFilter = NearestFilter; 14 | texture.needsUpdate = true; 15 | 16 | return texture; 17 | } 18 | -------------------------------------------------------------------------------- /packages/texture-loader/src/isImageBitmap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just a convenience function. We can't just 3 | * use 'image instanceof ImageBitmap' because ImageData may not be present in a 4 | * global scope. If it's not present in the global scope, then it would throw 5 | * an error instead of checking the type. 6 | */ 7 | export function isImageBitmap(image: any): image is ImageBitmap { 8 | return globalThis.ImageBitmap && image instanceof ImageBitmap; 9 | } 10 | -------------------------------------------------------------------------------- /packages/texture-loader/src/isImageData.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This function is just for consistency with `isImageBitmap`. 3 | * 4 | * @see isImageBitmap 5 | */ 6 | export function isImageData(image: any): image is ImageData { 7 | return image instanceof ImageData; 8 | } 9 | -------------------------------------------------------------------------------- /packages/texture-loader/src/keyFromTextureRequest.ts: -------------------------------------------------------------------------------- 1 | import type { TextureRequest } from "./TextureRequest.type"; 2 | 3 | export function keyFromTextureRequest(textureRequest: TextureRequest): string { 4 | return textureRequest.textureUrl; 5 | } 6 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/README.md: -------------------------------------------------------------------------------- 1 | # `three-css2d-renderer` 2 | 3 | Custom implementation of `three` `CSS2DRenderer` that is capable of running 4 | in the worker thread. Instead of rendering directly to DOM, it renders views 5 | supported by `dom-renderer` package and communicates via shared memory and 6 | message channels. 7 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/src/CSS2DObject.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Object3D } from "three/src/core/Object3D"; 2 | 3 | import type { DOMElementProps } from "../../dom-renderer/src/DOMElementProps.type"; 4 | import type { DOMElementsLookup } from "../../dom-renderer/src/DOMElementsLookup.type"; 5 | 6 | export interface CSS2DObject extends Object3D { 7 | readonly element: string & keyof L; 8 | readonly state: Float32Array; 9 | readonly type: "CSS2DObject"; 10 | 11 | isDirty: boolean; 12 | isDisposed: boolean; 13 | isRendered: boolean; 14 | props: DOMElementProps; 15 | } 16 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/src/CSS2DObjectState.ts: -------------------------------------------------------------------------------- 1 | import { CSS2DObjectStateIndices } from "./CSS2DObjectStateIndices.enum"; 2 | 3 | function createEmptyState(usesSharedBuffer: boolean): Float32Array { 4 | if (usesSharedBuffer) { 5 | return new Float32Array(new SharedArrayBuffer(CSS2DObjectStateIndices.__TOTAL * Float32Array.BYTES_PER_ELEMENT)); 6 | } 7 | 8 | return new Float32Array(CSS2DObjectStateIndices.__TOTAL); 9 | } 10 | 11 | export const CSS2DObjectState = Object.freeze({ 12 | createEmptyState: createEmptyState, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/src/CSS2DObjectStateIndices.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CSS2DObjectStateIndices { 2 | CAMERA_FAR, 3 | DISTANCE_TO_CAMERA_SQUARED, 4 | IS_RENDERED, 5 | TRANSLATE_X, 6 | TRANSLATE_Y, 7 | VERSION, 8 | VISIBLE, 9 | Z_INDEX, 10 | 11 | // This one NEEDS to be last to get the enum size correctly 12 | __TOTAL, 13 | } 14 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/src/CSS2DRenderer.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Camera } from "three/src/cameras/Camera"; 2 | import type { Scene } from "three/src/scenes/Scene"; 3 | 4 | import type { ResizeableRenderer } from "../../framework/src/ResizeableRenderer.interface"; 5 | 6 | import type { CSS2DRendererInfo } from "./CSS2DRendererInfo.type"; 7 | 8 | export interface CSS2DRenderer extends ResizeableRenderer { 9 | readonly info: CSS2DRendererInfo; 10 | 11 | render(scene: Scene, camera: Camera, updateMatrices: boolean): void; 12 | } 13 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/src/CSS2DRendererInfo.type.ts: -------------------------------------------------------------------------------- 1 | export type CSS2DRendererInfo = { 2 | render: { 3 | elements: number; 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/src/CSS2DRendererStatsReport.type.ts: -------------------------------------------------------------------------------- 1 | import type { StatsReport } from "../../framework/src/StatsReport.type"; 2 | 3 | export type CSS2DRendererStatsReport = StatsReport & { 4 | renderElements: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/three-css2d-renderer/src/isCSS2DObject.ts: -------------------------------------------------------------------------------- 1 | import type { Object3D } from "three/src/core/Object3D"; 2 | 3 | import type { DOMElementsLookup } from "../../dom-renderer/src/DOMElementsLookup.type"; 4 | 5 | import type { CSS2DObject } from "./CSS2DObject.interface"; 6 | 7 | export function isCSS2DObject(object: Object3D): object is CSS2DObject { 8 | return "CSS2DObject" === object.type; 9 | } 10 | -------------------------------------------------------------------------------- /packages/three-modules/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/three-modules/README.md: -------------------------------------------------------------------------------- 1 | # `three-modules` 2 | 3 | This repo contains modified or repackaged THREE example scripts. 4 | 5 | Primarily three things are done to those scripts: 6 | 7 | 1. They are modified to work within the offscreen worker. This especially 8 | applies to loaders. Instead of just returning mesh, they return typed arrays 9 | so they can be transfered into another place via Transferables and postMessage. 10 | 2. Resources disposing is implemented wherever it was misssing. For example 11 | postprocessing helpers did not dispose resources in mose cases which would lead 12 | to memory leaks when using them. 13 | 3. Examples use minimal imports directly from threejs library instead of 14 | including the entire THREE bundle. This can prevent about 500-600kB of unused 15 | scripts to be loaded. 16 | -------------------------------------------------------------------------------- /packages/three-modules/src/loaders/GeometryBoundingBox.type.ts: -------------------------------------------------------------------------------- 1 | export type GeometryBoundingBox = { 2 | min: { 3 | x: number; 4 | y: number; 5 | z: number; 6 | }; 7 | max: { 8 | x: number; 9 | y: number; 10 | z: number; 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/three-modules/src/loaders/MD2GeometryParts.type.ts: -------------------------------------------------------------------------------- 1 | export type MD2GeometryParts = { 2 | animations: { 3 | [key: string]: string; 4 | }; 5 | body: string; 6 | skins: Array; 7 | weapons: Array<[string, string]>; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/three-modules/src/loaders/MD2LoaderMorphNormal.type.ts: -------------------------------------------------------------------------------- 1 | export type MD2LoaderMorphNormal = { 2 | name: string; 3 | normals: Float32Array; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/three-modules/src/loaders/MD2LoaderMorphPosition.type.ts: -------------------------------------------------------------------------------- 1 | export type MD2LoaderMorphPosition = { 2 | name: string; 3 | positions: Float32Array; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/three-modules/src/loaders/MD2LoaderParsedGeometry.type.ts: -------------------------------------------------------------------------------- 1 | import type { GeometryBoundingBox } from "./GeometryBoundingBox.type"; 2 | import type { MD2LoaderMorphNormal } from "./MD2LoaderMorphNormal.type"; 3 | import type { MD2LoaderMorphPosition } from "./MD2LoaderMorphPosition.type"; 4 | import type { MD2LoaderParsedGeometryFrame } from "./MD2LoaderParsedGeometryFrame.type"; 5 | 6 | export type MD2LoaderParsedGeometry = { 7 | boundingBoxes: { 8 | stand: GeometryBoundingBox; 9 | }; 10 | frames: Array; 11 | morphNormals: Array; 12 | morphPositions: Array; 13 | normals: Float32Array; 14 | uvs: Float32Array; 15 | vertices: Float32Array; 16 | 17 | transferables: Array; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/three-modules/src/loaders/MD2LoaderParsedGeometryFrame.type.ts: -------------------------------------------------------------------------------- 1 | export type MD2LoaderParsedGeometryFrame = { 2 | name: string; 3 | normals: Array | Float32Array; 4 | vertices: Array | Float32Array; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/three-modules/src/loaders/MD2LoaderParsedGeometryWithParts.type.ts: -------------------------------------------------------------------------------- 1 | import type { MD2GeometryParts } from "./MD2GeometryParts.type"; 2 | import type { MD2LoaderParsedGeometry } from "./MD2LoaderParsedGeometry.type"; 3 | 4 | export type MD2LoaderParsedGeometryWithParts = MD2LoaderParsedGeometry & { 5 | parts: MD2GeometryParts; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/three-modules/src/misc/MorphBlendMesh.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Mesh } from "three/src/objects/Mesh"; 2 | 3 | import type { MainLoopUpdatable } from "../../../framework/src/MainLoopUpdatable.interface"; 4 | 5 | import type { MorphBlendMeshAnimation } from "./MorphBlendMeshAnimation.type"; 6 | 7 | export interface MorphBlendMesh extends MainLoopUpdatable, Mesh { 8 | animationsList: Array; 9 | 10 | autoCreateAnimations(fps: number): void; 11 | 12 | playAnimation(name: string): void; 13 | 14 | setAnimationDirectionBackward(name: string): void; 15 | 16 | setAnimationDirectionForward(name: string): void; 17 | 18 | setAnimationWeight(name: string, weight: number): void; 19 | 20 | setAnimationTime(name: string, time: number): void; 21 | 22 | stopAnimation(name: string): void; 23 | } 24 | -------------------------------------------------------------------------------- /packages/three-modules/src/misc/MorphBlendMeshAnimation.type.ts: -------------------------------------------------------------------------------- 1 | export type MorphBlendMeshAnimation = { 2 | active: boolean; 3 | currentFrame: number; 4 | direction: number; 5 | directionBackwards: boolean; 6 | duration: number; 7 | end: number; 8 | fps: number; 9 | lastFrame: number; 10 | length: number; 11 | mirroredLoop: boolean; 12 | name: string; 13 | start: number; 14 | time: number; 15 | weight: number; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/three-modules/src/postprocessing/ClearMaskPass.ts: -------------------------------------------------------------------------------- 1 | import { Pass } from "./Pass"; 2 | 3 | import type { WebGLRenderer } from "three/src/renderers/WebGLRenderer"; 4 | 5 | export class ClearMaskPass extends Pass { 6 | readonly clearMask: true = true; 7 | readonly needsSwap: false = false; 8 | 9 | dispose() {} 10 | 11 | render(renderer: WebGLRenderer) { 12 | renderer.state.buffers.stencil.setLocked(false); 13 | renderer.state.buffers.stencil.setTest(false); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/three-modules/src/postprocessing/EffectComposer.interface.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableGeneric } from "../../../framework/src/DisposableGeneric.interface"; 2 | import type { ResizeableRenderer } from "../../../framework/src/ResizeableRenderer.interface"; 3 | 4 | import type { Pass } from "./Pass.interface"; 5 | 6 | export interface EffectComposer extends DisposableGeneric, ResizeableRenderer { 7 | addPass(pass: Pass): void; 8 | 9 | isLastEnabledPass(passIndex: number): boolean; 10 | 11 | removePass(pass: Pass): void; 12 | 13 | render(deltaTime: number): void; 14 | 15 | setPixelRatio(pixelRatio: number): void; 16 | 17 | swapBuffers(): void; 18 | } 19 | -------------------------------------------------------------------------------- /packages/three-modules/src/postprocessing/Pass.ts: -------------------------------------------------------------------------------- 1 | import type { WebGLRenderer } from "three/src/renderers/WebGLRenderer"; 2 | import type { WebGLRenderTarget } from "three/src/renderers/WebGLRenderTarget"; 3 | 4 | import type { Pass as IPass } from "./Pass.interface"; 5 | 6 | export abstract class Pass implements IPass { 7 | readonly clearMask: boolean = false; 8 | readonly mask: boolean = false; 9 | readonly needsSwap: boolean = true; 10 | 11 | enabled: boolean = true; 12 | clear: boolean = false; 13 | 14 | abstract dispose(): void; 15 | 16 | abstract render( 17 | renderer: WebGLRenderer, 18 | renderToScreen: boolean, 19 | writeBuffer: WebGLRenderTarget, 20 | readBuffer: WebGLRenderTarget, 21 | deltaTime: number, 22 | maskActive: boolean 23 | ): void; 24 | 25 | setSize(width: number, height: number): void {} 26 | } 27 | -------------------------------------------------------------------------------- /packages/three-modules/src/unmountPass.ts: -------------------------------------------------------------------------------- 1 | import type { UnmountableCallback } from "../../framework/src/UnmountableCallback.type"; 2 | 3 | import type { EffectComposer } from "./postprocessing/EffectComposer.interface"; 4 | import type { Pass } from "./postprocessing/Pass.interface"; 5 | 6 | export function unmountPass(effectComposer: EffectComposer, pass: Pass): UnmountableCallback { 7 | return function () { 8 | effectComposer.removePass(pass); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /packages/three-morph-blend-mesh-mixer/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/three-morph-blend-mesh-mixer/README.md: -------------------------------------------------------------------------------- 1 | # `three-morph-blend-mesh-mixer` 2 | -------------------------------------------------------------------------------- /packages/three-morph-blend-mesh-mixer/src/MorphBlendMeshMixer.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | 3 | import type { MorphBlendMeshMixerState } from "./MorphBlendMeshMixerState.type"; 4 | 5 | export interface MorphBlendMeshMixer extends MainLoopUpdatable { 6 | readonly state: MorphBlendMeshMixerState; 7 | 8 | setAnimation(name: string): void; 9 | } 10 | -------------------------------------------------------------------------------- /packages/three-morph-blend-mesh-mixer/src/MorphBlendMeshMixerState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | 3 | export type MorphBlendMeshMixerState = MainLoopUpdatableState; 4 | -------------------------------------------------------------------------------- /packages/three-morph-blend-mesh-mixer/src/MorphBlendMeshTransition.interface.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 2 | 3 | import type { MorphBlendMeshTransitionState } from "./MorphBlendMeshTransitionState.type"; 4 | 5 | export interface MorphBlendMeshTransition extends MainLoopUpdatable { 6 | readonly state: MorphBlendMeshTransitionState; 7 | 8 | transitionTo(animationName: string): MorphBlendMeshTransition; 9 | } 10 | -------------------------------------------------------------------------------- /packages/three-morph-blend-mesh-mixer/src/MorphBlendMeshTransitionState.type.ts: -------------------------------------------------------------------------------- 1 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 2 | 3 | export type MorphBlendMeshTransitionState = MainLoopUpdatableState & { 4 | currentAnimation: string; 5 | targetAnimation: string; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/views/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /packages/views/README.md: -------------------------------------------------------------------------------- 1 | # `views` 2 | 3 | Bridge between a scene and game domain logic. 4 | -------------------------------------------------------------------------------- /packages/views/src/Interactable.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Object3D } from "three/src/core/Object3D"; 2 | 3 | import type { InteractableState } from "./InteractableState.type"; 4 | 5 | export interface Interactable { 6 | readonly interactableObject3D: Object3D; 7 | readonly isInteractable: true; 8 | readonly state: InteractableState; 9 | } 10 | -------------------------------------------------------------------------------- /packages/views/src/InteractableBag.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Interactable } from "./Interactable.interface"; 2 | 3 | export interface InteractableBag { 4 | readonly interactables: Set; 5 | } 6 | -------------------------------------------------------------------------------- /packages/views/src/InteractableBag.ts: -------------------------------------------------------------------------------- 1 | import type { Logger } from "loglevel"; 2 | 3 | import type { Interactable } from "./Interactable.interface"; 4 | import type { InteractableBag as IInteractableBag } from "./InteractableBag.interface"; 5 | 6 | export function InteractableBag(logger: Logger): IInteractableBag { 7 | const interactables: Set = new Set(); 8 | 9 | return Object.freeze({ 10 | interactables: interactables, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/views/src/InteractableState.type.ts: -------------------------------------------------------------------------------- 1 | export type InteractableState = { 2 | isInteracting: boolean; 3 | needsInteractions: boolean; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/views/src/ViewBag.interface.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable } from "../../framework/src/Disposable.interface"; 2 | import type { MainLoopUpdatable } from "../../framework/src/MainLoopUpdatable.interface"; 3 | import type { Mountable } from "../../framework/src/Mountable.interface"; 4 | import type { Pauseable } from "../../framework/src/Pauseable.interface"; 5 | import type { PollablePreloading } from "../../framework/src/PollablePreloading.interface"; 6 | import type { Preloadable } from "../../framework/src/Preloadable.interface"; 7 | 8 | import type { View } from "./View.interface"; 9 | import type { ViewBagState } from "./ViewBagState.type"; 10 | 11 | export interface ViewBag extends Disposable, MainLoopUpdatable, Mountable, Pauseable, PollablePreloading, Preloadable { 12 | readonly state: ViewBagState; 13 | readonly views: Set; 14 | } 15 | -------------------------------------------------------------------------------- /packages/views/src/ViewBagState.type.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableState } from "../../framework/src/DisposableState.type"; 2 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 3 | import type { MountableState } from "../../framework/src/MountableState.type"; 4 | import type { PauseableState } from "../../framework/src/PauseableState.type"; 5 | import type { PreloadableState } from "../../framework/src/PreloadableState.type"; 6 | 7 | export type ViewBagState = DisposableState & 8 | MainLoopUpdatableState & 9 | MountableState & 10 | PauseableState & 11 | PreloadableState; 12 | -------------------------------------------------------------------------------- /packages/views/src/ViewState.type.ts: -------------------------------------------------------------------------------- 1 | import type { DisposableState } from "../../framework/src/DisposableState.type"; 2 | import type { MainLoopUpdatableState } from "../../framework/src/MainLoopUpdatableState.type"; 3 | import type { MountableState } from "../../framework/src/MountableState.type"; 4 | import type { PauseableState } from "../../framework/src/PauseableState.type"; 5 | import type { PreloadableState } from "../../framework/src/PreloadableState.type"; 6 | import type { RaycastableState } from "../../input/src/RaycastableState.type"; 7 | 8 | import type { InteractableState } from "./InteractableState.type"; 9 | 10 | export type ViewState = DisposableState & 11 | InteractableState & 12 | MainLoopUpdatableState & 13 | MountableState & 14 | PauseableState & 15 | PreloadableState & 16 | RaycastableState; 17 | -------------------------------------------------------------------------------- /packages/views/src/createViewState.ts: -------------------------------------------------------------------------------- 1 | import type { ViewState } from "./ViewState.type"; 2 | 3 | export function createViewState(viewState: Partial = {}): ViewState { 4 | return Object.seal( 5 | Object.assign( 6 | { 7 | isDisposed: false, 8 | isInteracting: false, 9 | isMounted: false, 10 | isPaused: false, 11 | isPreloaded: false, 12 | isPreloading: false, 13 | isRayIntersecting: false, 14 | needsInteractions: false, 15 | needsRaycast: false, 16 | needsUpdates: false, 17 | }, 18 | viewState 19 | ) 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/views/src/isInteractable.ts: -------------------------------------------------------------------------------- 1 | import type { Interactable } from "./Interactable.interface"; 2 | import type { View } from "./View.interface"; 3 | 4 | export function isInteractable(view: View): view is View & Interactable { 5 | return true === (view as Interactable).isInteractable; 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require("autoprefixer"); 2 | const cssnano = require("cssnano"); 3 | const postcssImport = require("postcss-import"); 4 | 5 | module.exports = { 6 | plugins: [ 7 | postcssImport({ 8 | root: __dirname, 9 | }), 10 | autoprefixer, 11 | cssnano({ 12 | preset: "default", 13 | }), 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | /assets 2 | /cert.pem 3 | /key.pem 4 | /index.html 5 | /lib 6 | /service_worker.js 7 | /service_worker_*.js.map 8 | -------------------------------------------------------------------------------- /public/locales/en/characters.json: -------------------------------------------------------------------------------- 1 | { 2 | "label_gatekeeper": "Gatekeeper", 3 | "label_player": "Player" 4 | } 5 | -------------------------------------------------------------------------------- /public/locales/en/interactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "barter": "Barter", 3 | "talk": "Talk" 4 | } 5 | -------------------------------------------------------------------------------- /public/locales/en/objects.json: -------------------------------------------------------------------------------- 1 | { 2 | "label_bonfire": "Bonfire" 3 | } 4 | -------------------------------------------------------------------------------- /public/locales/pl/characters.json: -------------------------------------------------------------------------------- 1 | { 2 | "label_gatekeeper": "Strażnik Bramy", 3 | "label_player": "Gracz" 4 | } 5 | -------------------------------------------------------------------------------- /public/locales/pl/interactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "barter": "Handluj", 3 | "talk": "Rozmawiaj" 4 | } 5 | -------------------------------------------------------------------------------- /public/locales/pl/objects.json: -------------------------------------------------------------------------------- 1 | { 2 | "label_bonfire": "Ognisko" 3 | } 4 | -------------------------------------------------------------------------------- /public/locales/ru/characters.json: -------------------------------------------------------------------------------- 1 | { 2 | "label_gatekeeper": "Привратник", 3 | "label_player": "Игрок" 4 | } 5 | -------------------------------------------------------------------------------- /public/locales/ru/interactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "barter": "Бартер", 3 | "talk": "Говорить" 4 | } 5 | -------------------------------------------------------------------------------- /public/locales/ru/objects.json: -------------------------------------------------------------------------------- 1 | { 2 | "label_bonfire": "Костер" 3 | } 4 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "#000000", 3 | "categories": ["games", "entertainment"], 4 | "descrption": "Apocalyptic Adventure Game", 5 | "display": "standalone", 6 | "name": "Personal Idol", 7 | "orientation": "any", 8 | "scope": "/g/personalidol/", 9 | "start_url": "/g/personalidol/index.html", 10 | "theme_color": "#000000" 11 | } 12 | -------------------------------------------------------------------------------- /public/preload.js: -------------------------------------------------------------------------------- 1 | const uiRoot = document.getElementById("ui-root"); 2 | 3 | uiRoot.addEventListener("error", function (evt) { 4 | document.body.classList.add("bootstrap-fatal-error"); 5 | 6 | uiRoot.style.bottom = "0"; 7 | uiRoot.style.right = "0"; 8 | uiRoot.innerHTML = ` 9 |
10 |
11 | Error: ${evt.detail.message} 12 |
13 |
14 | `; 15 | }); 16 | -------------------------------------------------------------------------------- /scripts/_notify.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | NOTIFICATION_TIMEOUT_MS=3000; 4 | 5 | notify() { 6 | # notify-send -t $NOTIFICATION_TIMEOUT_MS "${1}: ${2}" "${3}"; 7 | echo "${1}: ${2}" "${3}"; 8 | } 9 | -------------------------------------------------------------------------------- /scripts/helper_build_id.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | GIT_LAST_COMMIT_ID=$(git log --format="%H" -n 1); 4 | 5 | git diff-index --quiet HEAD; 6 | 7 | GIT_HAS_CHANGES=$?; 8 | 9 | BUILD_ID_APPEND=""; 10 | 11 | if [ $GIT_HAS_CHANGES ]; then 12 | # Append hash of changes. 13 | BUILD_ID_APPEND="_$( git diff | md5sum | cut -d ' ' -f 1 )"; 14 | fi 15 | 16 | BUILD_ID="${GIT_LAST_COMMIT_ID}${BUILD_ID_APPEND}"; 17 | 18 | echo $BUILD_ID 19 | -------------------------------------------------------------------------------- /scripts/watch_trigger.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | BASE_DIRECTORY=$(realpath $PWD/scripts) 4 | MAKE=$1 5 | WATCH_PATTERN=$2 6 | WATCH_TARGET=$3 7 | 8 | . ${BASE_DIRECTORY}/_notify.sh 9 | 10 | PACKAGE_NAME=$(basename $(pwd)) 11 | PACKAGE_SCOPE="personalidol" 12 | BUILD_NAME="${PACKAGE_SCOPE}/${PACKAGE_NAME}" 13 | 14 | LAST="" 15 | 16 | inotifywait -mqr -e create,delete,modify,move $WATCH_PATTERN --format "%T %w%f" --timefmt "%F %T" | while read EVENT; do 17 | if [ "$LAST" != "$EVENT" ]; then 18 | notify "Triggered" $BUILD_NAME $WATCH_TARGET 19 | 20 | if $MAKE $WATCH_TARGET; then 21 | notify "Done" $BUILD_NAME $WATCH_TARGET 22 | else 23 | notify "Failure" $BUILD_NAME $WATCH_TARGET 24 | fi 25 | fi 26 | LAST=$EVENT 27 | done 28 | -------------------------------------------------------------------------------- /tiled/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | -------------------------------------------------------------------------------- /tiled/README.md: -------------------------------------------------------------------------------- 1 | # `tiled` 2 | -------------------------------------------------------------------------------- /tiled/fixtures/tileset-meta-passability.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /trenchbroom/Makefile: -------------------------------------------------------------------------------- 1 | ~/.TrenchBroom/games/PersonalIdol: 2 | mkdir -p ~/.TrenchBroom/games/PersonalIdol 3 | 4 | ~/.TrenchBroom/games/PersonalIdol/GameConfig.cfg: ~/.TrenchBroom/games/PersonalIdol ./GameConfig.cfg 5 | install ./GameConfig.cfg ~/.TrenchBroom/games/PersonalIdol/GameConfig.cfg 6 | 7 | ~/.TrenchBroom/games/PersonalIdol/PersonalIdol.fgd: ~/.TrenchBroom/games/PersonalIdol ./PersonalIdol.fgd 8 | install ./PersonalIdol.fgd ~/.TrenchBroom/games/PersonalIdol/PersonalIdol.fgd 9 | 10 | .PHONY: install 11 | install: ~/.TrenchBroom/games/PersonalIdol/GameConfig.cfg ~/.TrenchBroom/games/PersonalIdol/PersonalIdol.fgd 12 | --------------------------------------------------------------------------------