early shadows
This commit is contained in:
parent
9eba227ec3
commit
b5f3d9e4eb
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -5609,6 +5609,11 @@
|
|||
"invert-kv": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"line-intersect": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/line-intersect/-/line-intersect-2.2.1.tgz",
|
||||
"integrity": "sha512-uOPErCqtEnHYsnesl56XmKm9nWF27kOqZCuibJEkyAJ23FHsKmOHo8FPT6SRAp2h3wzvyXJNjbPsq9FF5x29vw=="
|
||||
},
|
||||
"load-json-file": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"dependencies": {
|
||||
"@eix-js/utils": "0.0.6",
|
||||
"deepmerge": "^4.0.0",
|
||||
"line-intersect": "^2.2.1",
|
||||
"mainloop.js": "^1.0.4",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Singleton } from '@eix-js/utils'
|
||||
import { Observable, fromEvent, BehaviorSubject } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { multiply } from '../../vector2/helpers/basic'
|
||||
|
||||
@Singleton
|
||||
export class Screen {
|
||||
|
@ -25,4 +26,8 @@ export class Screen {
|
|||
public get y() {
|
||||
return this.height.value
|
||||
}
|
||||
|
||||
public get center() {
|
||||
return multiply([this.x, this.y], 0.5)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Transform, vector2 } from './Transform'
|
||||
import { Transform } from './Transform'
|
||||
|
||||
export class Gate {
|
||||
public static lastId = 0
|
||||
public transform = new Transform()
|
||||
public id = Gate.lastId++
|
||||
public shadow: vector2 = [0, 0]
|
||||
|
||||
public constructor(public color = 'blue') {}
|
||||
}
|
||||
|
|
|
@ -5,26 +5,17 @@ import { MouseEventInfo } from '../../core/components/FluidCanvas'
|
|||
import { pointInSquare } from '../helpers/pointInSquare'
|
||||
import { vector2 } from './Transform'
|
||||
import merge from 'deepmerge'
|
||||
import { smoothStep } from '../../vector2/helpers/smoothStep'
|
||||
import { renderGate } from '../helpers/renderGate'
|
||||
import { renderGateShadow } from '../helpers/renderGateShadow'
|
||||
import { Gate } from './Gate'
|
||||
import { MouseManager } from './MouseManager'
|
||||
import { Screen } from '../../core/classes/Screen'
|
||||
import {
|
||||
add,
|
||||
invert,
|
||||
ofLength,
|
||||
length,
|
||||
multiply
|
||||
} from '../../vector2/helpers/basic'
|
||||
|
||||
export interface SimulationRendererOptions {
|
||||
shadows: {
|
||||
enabled: boolean
|
||||
color: string
|
||||
offset: number
|
||||
speed: number
|
||||
lightHeight: number
|
||||
gateHeight: number
|
||||
}
|
||||
dnd: {
|
||||
rotation: number
|
||||
|
@ -35,8 +26,8 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
|||
shadows: {
|
||||
enabled: true,
|
||||
color: 'rgba(0,0,0,0.3)',
|
||||
offset: 15,
|
||||
speed: 1
|
||||
gateHeight: 10,
|
||||
lightHeight: 50
|
||||
},
|
||||
dnd: {
|
||||
rotation: Math.PI / 12 // 7.5 degrees
|
||||
|
@ -133,13 +124,22 @@ export class SimulationRenderer {
|
|||
public render(ctx: CanvasRenderingContext2D) {
|
||||
this.clear(ctx)
|
||||
|
||||
const center = this.screen.center
|
||||
|
||||
// render gates
|
||||
for (const gate of this.simulation.gates) {
|
||||
renderGate(ctx, gate)
|
||||
if (this.options.shadows.enabled) {
|
||||
renderGateShadow(ctx, this.options.shadows.color, gate)
|
||||
renderGateShadow(
|
||||
ctx,
|
||||
this.options.shadows.color,
|
||||
gate,
|
||||
this.options.shadows.gateHeight,
|
||||
[center[0], center[1], this.options.shadows.lightHeight]
|
||||
)
|
||||
}
|
||||
|
||||
renderGate(ctx, gate)
|
||||
// renderGate(ctx, gate)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,32 +152,7 @@ export class SimulationRenderer {
|
|||
return this.simulation.gates.get(id)
|
||||
}
|
||||
|
||||
public getOptimalShadow(gate: Gate) {
|
||||
const center = multiply([this.screen.x, this.screen.y] as vector2, 0.5)
|
||||
|
||||
const difference = add(center, invert(gate.transform.position))
|
||||
|
||||
return add(
|
||||
add(difference, center),
|
||||
ofLength(difference, this.options.shadows.offset)
|
||||
)
|
||||
}
|
||||
|
||||
public getShadowPosition(gate: Gate) {
|
||||
return gate.transform.position.map(
|
||||
(value, index) => value - this.getOptimalShadow(gate)[index]
|
||||
) as vector2
|
||||
}
|
||||
|
||||
public update(delta: number) {
|
||||
for (const gate of this.simulation.gates) {
|
||||
gate.shadow = smoothStep(
|
||||
this.options.shadows.speed,
|
||||
gate.shadow,
|
||||
this.getShadowPosition(gate)
|
||||
)
|
||||
}
|
||||
|
||||
const selected = this.getSelected()
|
||||
|
||||
if (selected && this.movedSelection) {
|
||||
|
@ -187,6 +162,10 @@ export class SimulationRenderer {
|
|||
} else {
|
||||
this.mouseManager.update()
|
||||
}
|
||||
|
||||
// for (const gate of this.simulation.gates) {
|
||||
// gate.transform.rotation += 0.01
|
||||
// }
|
||||
}
|
||||
|
||||
public getSelected() {
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
import { BehaviorSubject } from 'rxjs'
|
||||
import { allCombinations } from '../helpers/allCombinations'
|
||||
import { rotateAroundVector } from '../../vector2/helpers/rotate'
|
||||
|
||||
export type vector2 = [number, number]
|
||||
export type vector3 = [number, number, number]
|
||||
export type vector4 = [number, number, number, number]
|
||||
export type vector8 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number
|
||||
]
|
||||
|
||||
export class Transform {
|
||||
public constructor(
|
||||
|
@ -14,6 +27,40 @@ export class Transform {
|
|||
return [...this.position, ...this.scale] as vector4
|
||||
}
|
||||
|
||||
public getPoints() {
|
||||
const combinations = Array.from(allCombinations([0, 1], [0, 1]))
|
||||
|
||||
// those are not in the right order
|
||||
const points = combinations.map(combination => [
|
||||
this.x + this.height * combination[0],
|
||||
this.y + this.width * combination[1]
|
||||
])
|
||||
|
||||
const pointsInTheRightOrder = [
|
||||
points[0],
|
||||
points[1],
|
||||
points[3],
|
||||
points[2]
|
||||
] as vector2[]
|
||||
|
||||
const result = pointsInTheRightOrder.map(point =>
|
||||
rotateAroundVector(point, this.center, this.rotation)
|
||||
) as vector2[]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public getEdges() {
|
||||
const points = this.getPoints()
|
||||
const edges = []
|
||||
|
||||
for (let index = 0; index < points.length; index++) {
|
||||
edges.push([points[index], points[(index + 1) % points.length]])
|
||||
}
|
||||
|
||||
return edges as [vector2, vector2][]
|
||||
}
|
||||
|
||||
/** Short forms for random stuff */
|
||||
|
||||
get x() {
|
||||
|
@ -40,6 +87,10 @@ export class Transform {
|
|||
return this.y + this.height
|
||||
}
|
||||
|
||||
get center() {
|
||||
return [this.x + this.width / 2, this.y + this.height / 2] as vector2
|
||||
}
|
||||
|
||||
set x(value: number) {
|
||||
this.position = [value, this.y]
|
||||
}
|
||||
|
|
8
src/modules/simulation/helpers/allCombinations.ts
Normal file
8
src/modules/simulation/helpers/allCombinations.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export function* allCombinations<T>(first: T[], second: T[]): Iterable<[T, T]> {
|
||||
for (const item of first) {
|
||||
// TODO: change name
|
||||
for (const element of second) {
|
||||
yield [item, element]
|
||||
}
|
||||
}
|
||||
}
|
23
src/modules/simulation/helpers/drawPolygon.ts
Normal file
23
src/modules/simulation/helpers/drawPolygon.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { vector2 } from '../classes/Transform'
|
||||
|
||||
export const drawPolygon = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
points: vector2[],
|
||||
fill = true,
|
||||
stroke = false
|
||||
) => {
|
||||
ctx.beginPath()
|
||||
|
||||
for (const point of points) {
|
||||
ctx.lineTo(...point)
|
||||
}
|
||||
|
||||
ctx.closePath()
|
||||
|
||||
if (fill) {
|
||||
ctx.fill()
|
||||
}
|
||||
if (stroke) {
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
|
@ -2,30 +2,16 @@ import { Transform } from '../classes/Transform'
|
|||
|
||||
export const drawRotatedSquare = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
{ position, scale, rotation }: Transform,
|
||||
rotationMode = 0
|
||||
{ position, scale, rotation }: Transform
|
||||
) => {
|
||||
ctx.save()
|
||||
|
||||
ctx.translate(...position)
|
||||
|
||||
if (rotationMode === 0) {
|
||||
ctx.translate(scale[0] / 2, scale[1] / 2)
|
||||
} else if (rotationMode === 1) {
|
||||
ctx.translate(scale[0], scale[1])
|
||||
} else if (rotationMode === 1) {
|
||||
ctx.translate(0, scale[1])
|
||||
}
|
||||
|
||||
ctx.rotate(rotation)
|
||||
|
||||
if (rotationMode === 0) {
|
||||
ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale)
|
||||
} else if (rotationMode === 1) {
|
||||
ctx.fillRect(-scale[0], -scale[1], ...scale)
|
||||
} else if (rotationMode === -1) {
|
||||
ctx.fillRect(0, 0, ...scale)
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
|
8
src/modules/simulation/helpers/projectPoint.ts
Normal file
8
src/modules/simulation/helpers/projectPoint.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { vector3, vector2 } from '../classes/Transform'
|
||||
|
||||
export const projectPointOnPlane = (point: vector3, light: vector3) =>
|
||||
point.slice(0, 2).map((position, index) => {
|
||||
const delta = light[index] - position
|
||||
|
||||
return light[index] - (delta + (point[2] * delta) / light[2])
|
||||
}) as vector2
|
|
@ -1,13 +1,7 @@
|
|||
import { Gate } from '../classes/Gate'
|
||||
import { drawRotatedSquare } from './drawRotatedSquare'
|
||||
import { MouseManager } from '../classes/MouseManager'
|
||||
|
||||
export const renderGate = (ctx: CanvasRenderingContext2D, gate: Gate) => {
|
||||
let mode = 0
|
||||
|
||||
if (gate.transform.rotation > 0) mode = 1
|
||||
else if (gate.transform.rotation < 0) mode = -1
|
||||
|
||||
ctx.fillStyle = gate.color
|
||||
drawRotatedSquare(ctx, gate.transform, mode)
|
||||
drawRotatedSquare(ctx, gate.transform)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,110 @@
|
|||
import { Gate } from '../classes/Gate'
|
||||
import { vector2, Transform } from '../classes/Transform'
|
||||
import { clamp } from './clamp'
|
||||
import { drawRotatedSquare } from './drawRotatedSquare'
|
||||
import { projectPointOnPlane } from './projectPoint'
|
||||
import { drawPolygon } from './drawPolygon'
|
||||
import { vector3, vector2, vector4, vector8 } from '../classes/Transform'
|
||||
import { checkIntersection } from 'line-intersect'
|
||||
import { reverseArray } from './reverseArray'
|
||||
import { length, add, invert } from '../../vector2/helpers/basic'
|
||||
|
||||
export const pointRecivesLight = (
|
||||
points: vector2[], //this needs to have an even length
|
||||
light: vector3,
|
||||
index: number,
|
||||
ctx: CanvasRenderingContext2D
|
||||
) => {
|
||||
const point = points[index]
|
||||
const oposittePoint = points[(index + points.length / 2) % points.length]
|
||||
|
||||
const edgesToCheck = [
|
||||
[oposittePoint, points[(index + 1) % points.length]],
|
||||
[oposittePoint, points[index === 0 ? points.length - 1 : index - 1]]
|
||||
].map(points => points.flat() as vector4)
|
||||
|
||||
for (const edge of edgesToCheck) {
|
||||
const intersectionCheckParameters = [
|
||||
[light[0], light[1]],
|
||||
point,
|
||||
edge
|
||||
].flat() as vector8
|
||||
|
||||
const result = checkIntersection(...intersectionCheckParameters).type
|
||||
|
||||
if (result === 'intersecting') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export const renderGateShadow = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
color: string,
|
||||
gate: Gate
|
||||
gate: Gate,
|
||||
gateHeight: number,
|
||||
light: vector3
|
||||
) => {
|
||||
const scale = gate.transform.scale
|
||||
|
||||
ctx.fillStyle = color
|
||||
|
||||
drawRotatedSquare(
|
||||
ctx,
|
||||
new Transform(gate.shadow, scale, gate.transform.rotation)
|
||||
const points = gate.transform.getPoints()
|
||||
const exposedPoints = points.filter((point, index) =>
|
||||
pointRecivesLight(points, light, index, ctx)
|
||||
)
|
||||
|
||||
let includedPoints = [...points]
|
||||
|
||||
if (exposedPoints.length === 3) {
|
||||
let min = Infinity
|
||||
let current: null | vector2 = null
|
||||
|
||||
for (const point of exposedPoints) {
|
||||
const size = length(
|
||||
add(point, invert(light.slice(0, 2) as vector2))
|
||||
)
|
||||
|
||||
if (size < min) {
|
||||
min = size
|
||||
current = point
|
||||
}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
includedPoints.splice(includedPoints.indexOf(current), 1)
|
||||
}
|
||||
|
||||
if (
|
||||
includedPoints[0][1] < light[1] &&
|
||||
includedPoints[1][1] < light[1] &&
|
||||
!(includedPoints[2][1] > light[1])
|
||||
) {
|
||||
const temporary = includedPoints[0]
|
||||
includedPoints[0] = includedPoints[1]
|
||||
includedPoints[1] = temporary
|
||||
}
|
||||
}
|
||||
|
||||
if (exposedPoints.length === 2) {
|
||||
includedPoints = points.filter(
|
||||
point => exposedPoints.indexOf(point) === -1
|
||||
)
|
||||
}
|
||||
|
||||
const projections = includedPoints.map(point =>
|
||||
// ts doesnt let me do [...point, gateHeight]
|
||||
projectPointOnPlane([point[0], point[1], gateHeight], light)
|
||||
)
|
||||
|
||||
const polygon = [includedPoints, reverseArray(projections)].flat()
|
||||
|
||||
drawPolygon(ctx, polygon)
|
||||
|
||||
ctx.fillStyle = 'red'
|
||||
for (const point of [...includedPoints, ...projections, light]) {
|
||||
ctx.beginPath()
|
||||
ctx.ellipse(point[0], point[1], 10, 10, 0, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
ctx.strokeStyle = 'yellow'
|
||||
drawPolygon(ctx, points, false, true)
|
||||
}
|
||||
|
|
10
src/modules/simulation/helpers/reverseArray.ts
Normal file
10
src/modules/simulation/helpers/reverseArray.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export const reverseArray = <T>(array: T[]) => {
|
||||
const arr: T[] = []
|
||||
|
||||
for (let index = array.length - 1; index >= 0; index--) {
|
||||
const element = array[index]
|
||||
arr.push(element)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
|
@ -2,8 +2,15 @@ import { vector2 } from '../../simulation/classes/Transform'
|
|||
|
||||
// Basic stuff for arrays
|
||||
|
||||
export const add = (first: vector2, second: vector2) =>
|
||||
first.map((value, index) => value + second[index]) as vector2
|
||||
// If i don't say vector2 as the type adnotation
|
||||
// ts will throw some errors (because this is recursive)
|
||||
export const add = (...vectors: vector2[]): vector2 => {
|
||||
const first = vectors[0]
|
||||
const others = vectors.slice(1)
|
||||
const othersSum = others.length > 1 ? add(...others) : others[0]
|
||||
|
||||
return first.map((value, index) => value + othersSum[index]) as vector2
|
||||
}
|
||||
|
||||
export const invert = (vector: vector2) => vector.map(val => -val) as vector2
|
||||
|
||||
|
|
22
src/modules/vector2/helpers/rotate.ts
Normal file
22
src/modules/vector2/helpers/rotate.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { vector2 } from '../../simulation/classes/Transform'
|
||||
import { add, invert } from './basic'
|
||||
|
||||
const { cos, sin } = Math
|
||||
|
||||
export const rotate = (vector: vector2, angle: number): vector2 => {
|
||||
const x = cos(angle) * vector[0] - sin(angle) * vector[1]
|
||||
const y = sin(angle) * vector[0] + cos(angle) * vector[1]
|
||||
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
export const rotateAroundVector = (
|
||||
vector: vector2,
|
||||
around: vector2,
|
||||
angle: number
|
||||
) => {
|
||||
const translated = add(vector, invert(around))
|
||||
const rotated = rotate(translated, angle)
|
||||
|
||||
return add(rotated, around)
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": { "*": ["types/*"] },
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
|
|
13
types/line-intersect.d.ts
vendored
Normal file
13
types/line-intersect.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
export const checkIntersection: (
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
x3: number,
|
||||
y3: number,
|
||||
x4: number,
|
||||
y4: number
|
||||
) => {
|
||||
type: 'colinear' | 'parallel' | 'none' | 'intersecting'
|
||||
}
|
||||
export const colinearPointWithinSegment: any
|
Loading…
Reference in a new issue